mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 08:02:49 -05:00
Merge branch 'main' into km/pm-10600
# Conflicts: # src/Core/NotificationHub/NotificationHubPushRegistrationService.cs
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
|
@ -0,0 +1,75 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class FreeFamiliesForEnterprisePolicyValidatorTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_DoesNotNotifyUserWhenPolicyDisabled(
|
||||
Organization organization,
|
||||
List<OrganizationSponsorship> organizationSponsorships,
|
||||
[PolicyUpdate(PolicyType.FreeFamiliesSponsorshipPolicy)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.FreeFamiliesSponsorshipPolicy, true)] Policy policy,
|
||||
SutProvider<FreeFamiliesForEnterprisePolicyValidator> sutProvider)
|
||||
{
|
||||
|
||||
policy.Enabled = true;
|
||||
policyUpdate.Enabled = false;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organizationSponsorships);
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||
.SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(organizationSponsorships[0].FriendlyName, organizationSponsorships[0].ValidUntil.ToString(),
|
||||
organizationSponsorships[0].SponsoredOrganizationId.ToString(), organization.DisplayName());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_DoesNotifyUserWhenPolicyDisabled(
|
||||
Organization organization,
|
||||
List<OrganizationSponsorship> organizationSponsorships,
|
||||
[PolicyUpdate(PolicyType.FreeFamiliesSponsorshipPolicy)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.FreeFamiliesSponsorshipPolicy, true)] Policy policy,
|
||||
SutProvider<FreeFamiliesForEnterprisePolicyValidator> sutProvider)
|
||||
{
|
||||
|
||||
policy.Enabled = false;
|
||||
policyUpdate.Enabled = true;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organizationSponsorships);
|
||||
// Act
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
// Assert
|
||||
var offerAcceptanceDate = organizationSponsorships[0].ValidUntil!.Value.AddDays(-7).ToString("MM/dd/yyyy");
|
||||
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||
.SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(organizationSponsorships[0].FriendlyName, offerAcceptanceDate,
|
||||
organizationSponsorships[0].SponsoredOrganizationId.ToString(), organization.Name);
|
||||
|
||||
}
|
||||
}
|
@ -420,8 +420,6 @@ public class OrganizationServiceTests
|
||||
OrganizationSignup signup,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling).Returns(true);
|
||||
|
||||
signup.Plan = PlanType.TeamsMonthly;
|
||||
|
||||
var (organization, _, _) = await sutProvider.Sut.SignupClientAsync(signup);
|
||||
|
@ -842,6 +842,6 @@ public class PolicyServiceTests
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy, null));
|
||||
|
||||
Assert.Equal("Organization has verified domains.", badRequestException.Message);
|
||||
Assert.Equal("The Single organization policy is required for organizations that have enabled domain verification.", badRequestException.Message);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
@ -19,7 +19,6 @@ public abstract class BaseTokenProviderTests<T>
|
||||
{
|
||||
public abstract TwoFactorProviderType TwoFactorProviderType { get; }
|
||||
|
||||
#region Helpers
|
||||
protected static IEnumerable<object[]> SetupCanGenerateData(params (Dictionary<string, object> MetaData, bool ExpectedResponse)[] data)
|
||||
{
|
||||
return data.Select(d =>
|
||||
@ -48,6 +47,9 @@ public abstract class BaseTokenProviderTests<T>
|
||||
userService
|
||||
.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType, user)
|
||||
.Returns(true);
|
||||
userService
|
||||
.CanAccessPremium(user)
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
protected static UserManager<User> SubstituteUserManager()
|
||||
@ -76,7 +78,6 @@ public abstract class BaseTokenProviderTests<T>
|
||||
|
||||
user.TwoFactorProviders = JsonHelpers.LegacySerialize(providers);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public virtual async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
|
||||
User user, SutProvider<T> sutProvider)
|
||||
|
@ -0,0 +1,262 @@
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Duo = DuoUniversal;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Identity;
|
||||
|
||||
public class DuoUniversalTwoFactorTokenProviderTests : BaseTokenProviderTests<DuoUniversalTokenProvider>
|
||||
{
|
||||
private readonly IDuoUniversalTokenService _duoUniversalTokenService = Substitute.For<IDuoUniversalTokenService>();
|
||||
public override TwoFactorProviderType TwoFactorProviderType => TwoFactorProviderType.Duo;
|
||||
|
||||
public static IEnumerable<object[]> CanGenerateTwoFactorTokenAsyncData
|
||||
=> SetupCanGenerateData(
|
||||
( // correct data
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["ClientId"] = new string('c', 20),
|
||||
["ClientSecret"] = new string('s', 40),
|
||||
["Host"] = "https://api-abcd1234.duosecurity.com",
|
||||
},
|
||||
true
|
||||
),
|
||||
( // correct data duo federal
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["ClientId"] = new string('c', 20),
|
||||
["ClientSecret"] = new string('s', 40),
|
||||
["Host"] = "https://api-abcd1234.duofederal.com",
|
||||
},
|
||||
true
|
||||
),
|
||||
( // correct data duo federal
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["ClientId"] = new string('c', 20),
|
||||
["ClientSecret"] = new string('s', 40),
|
||||
["Host"] = "https://api-abcd1234.duofederal.com",
|
||||
},
|
||||
true
|
||||
),
|
||||
( // invalid host
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["ClientId"] = new string('c', 20),
|
||||
["ClientSecret"] = new string('s', 40),
|
||||
["Host"] = "",
|
||||
},
|
||||
false
|
||||
),
|
||||
( // clientId missing
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["ClientSecret"] = new string('s', 40),
|
||||
["Host"] = "https://api-abcd1234.duofederal.com",
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
public static IEnumerable<object[]> NonPremiumCanGenerateTwoFactorTokenAsyncData
|
||||
=> SetupCanGenerateData(
|
||||
( // correct data
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["ClientId"] = new string('c', 20),
|
||||
["ClientSecret"] = new string('s', 40),
|
||||
["Host"] = "https://api-abcd1234.duosecurity.com",
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
[Theory, BitMemberAutoData(nameof(CanGenerateTwoFactorTokenAsyncData))]
|
||||
public override async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.Premium = true;
|
||||
user.PremiumExpirationDate = DateTime.UtcNow.AddDays(1);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(expectedResponse);
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider);
|
||||
}
|
||||
|
||||
[Theory, BitMemberAutoData(nameof(NonPremiumCanGenerateTwoFactorTokenAsyncData))]
|
||||
public async Task CanGenerateTwoFactorTokenAsync_UserCanNotAccessPremium_ReturnsNull(Dictionary<string, object> metaData, bool expectedResponse,
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.Premium = false;
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(expectedResponse);
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GenerateToken_Success_ReturnsAuthUrl(
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider, string authUrl)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperDuoUniversalTokenService(user, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.GenerateAuthUrl(
|
||||
Arg.Any<Duo.Client>(),
|
||||
Arg.Any<IDataProtectorTokenFactory<DuoUserStateTokenable>>(),
|
||||
user)
|
||||
.Returns(authUrl);
|
||||
|
||||
// Act
|
||||
var token = await sutProvider.Sut.GenerateAsync("purpose", SubstituteUserManager(), user);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(token);
|
||||
Assert.Equal(token, authUrl);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GenerateToken_DuoClientNull_ReturnsNull(
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.Premium = true;
|
||||
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
|
||||
AdditionalSetup(sutProvider, user);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.BuildDuoTwoFactorClientAsync(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(null as Duo.Client);
|
||||
|
||||
// Act
|
||||
var token = await sutProvider.Sut.GenerateAsync("purpose", SubstituteUserManager(), user);
|
||||
|
||||
// Assert
|
||||
Assert.Null(token);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GenerateToken_UserCanNotAccessPremium_ReturnsNull(
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.Premium = false;
|
||||
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
|
||||
AdditionalSetup(sutProvider, user);
|
||||
|
||||
// Act
|
||||
var token = await sutProvider.Sut.GenerateAsync("purpose", SubstituteUserManager(), user);
|
||||
|
||||
// Assert
|
||||
Assert.Null(token);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateToken_ValidToken_ReturnsTrue(
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider, string token)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperDuoUniversalTokenService(user, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.RequestDuoValidationAsync(
|
||||
Arg.Any<Duo.Client>(),
|
||||
Arg.Any<IDataProtectorTokenFactory<DuoUserStateTokenable>>(),
|
||||
user,
|
||||
token)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var response = await sutProvider.Sut.ValidateAsync("purpose", token, SubstituteUserManager(), user);
|
||||
|
||||
// Assert
|
||||
Assert.True(response);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateToken_DuoClientNull_ReturnsFalse(
|
||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider, string token)
|
||||
{
|
||||
user.Premium = true;
|
||||
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
|
||||
AdditionalSetup(sutProvider, user);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.BuildDuoTwoFactorClientAsync(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(null as Duo.Client);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync("purpose", token, SubstituteUserManager(), user);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the IDuoUniversalTokenService is properly setup for the test.
|
||||
/// This ensures that the private GetDuoClientAsync, and GetDuoTwoFactorProvider
|
||||
/// methods will return true enabling the test to execute on the correct path.
|
||||
/// </summary>
|
||||
/// <param name="user">user from calling test</param>
|
||||
/// <param name="sutProvider">self</param>
|
||||
private void SetUpProperDuoUniversalTokenService(User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
user.Premium = true;
|
||||
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
|
||||
var client = BuildDuoClient();
|
||||
|
||||
AdditionalSetup(sutProvider, user);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.BuildDuoTwoFactorClientAsync(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(client);
|
||||
}
|
||||
|
||||
private Duo.Client BuildDuoClient()
|
||||
{
|
||||
var clientId = new string('c', 20);
|
||||
var clientSecret = new string('s', 40);
|
||||
return new Duo.ClientBuilder(clientId, clientSecret, "api-abcd1234.duosecurity.com", "redirectUrl").Build();
|
||||
}
|
||||
|
||||
private string GetTwoFactorDuoProvidersJson()
|
||||
{
|
||||
return
|
||||
"{\"2\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
@ -0,0 +1,289 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Duo = DuoUniversal;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Identity;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationDuoUniversalTwoFactorTokenProviderTests
|
||||
{
|
||||
private readonly IDuoUniversalTokenService _duoUniversalTokenService = Substitute.For<IDuoUniversalTokenService>();
|
||||
private readonly IDataProtectorTokenFactory<DuoUserStateTokenable> _tokenDataFactory = Substitute.For<IDataProtectorTokenFactory<DuoUserStateTokenable>>();
|
||||
|
||||
// Happy path
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CanGenerateTwoFactorTokenAsync_ReturnsTrue(
|
||||
Organization organization, SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.Enabled = true;
|
||||
organization.Use2fa = true;
|
||||
SetUpProperOrganizationDuoUniversalTokenService(null, organization, sutProvider);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(organization);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CanGenerateTwoFactorTokenAsync_DuoTwoFactorNotEnabled_ReturnsFalse(
|
||||
Organization organization, SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderNotEnabledJson();
|
||||
organization.Use2fa = true;
|
||||
organization.Enabled = true;
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(true);
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(null);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CanGenerateTwoFactorTokenAsync_BadMetaData_ProviderNull_ReturnsFalse(
|
||||
Organization organization, SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderJson();
|
||||
organization.Use2fa = true;
|
||||
organization.Enabled = true;
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(false);
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(null);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetDuoTwoFactorProvider_OrganizationNull_ReturnsNull(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(null);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetDuoTwoFactorProvider_OrganizationNotEnabled_ReturnsNull(
|
||||
Organization organization, SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperOrganizationDuoUniversalTokenService(null, organization, sutProvider);
|
||||
organization.Enabled = false;
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(organization);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetDuoTwoFactorProvider_OrganizationUse2FAFalse_ReturnsNull(
|
||||
Organization organization, SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperOrganizationDuoUniversalTokenService(null, organization, sutProvider);
|
||||
organization.Use2fa = false;
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(organization);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetDuoClient_ProviderNull_ReturnsNull(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GenerateAsync(null, default);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetDuoClient_DuoClientNull_ReturnsNull(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderJson();
|
||||
organization.Use2fa = true;
|
||||
organization.Enabled = true;
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.BuildDuoTwoFactorClientAsync(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(null as Duo.Client);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GenerateAsync(organization, default);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GenerateAsync_ReturnsAuthUrl(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider,
|
||||
Organization organization,
|
||||
User user,
|
||||
string AuthUrl)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperOrganizationDuoUniversalTokenService(BuildDuoClient(), organization, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.GenerateAuthUrl(Arg.Any<Duo.Client>(), Arg.Any<IDataProtectorTokenFactory<DuoUserStateTokenable>>(), user)
|
||||
.Returns(AuthUrl);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GenerateAsync(organization, user);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(AuthUrl, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GenerateAsync_ClientNull_ReturnsNull(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider,
|
||||
Organization organization,
|
||||
User user)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperOrganizationDuoUniversalTokenService(null, organization, sutProvider);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GenerateAsync(organization, user);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_TokenValid_ReturnsTrue(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider,
|
||||
Organization organization,
|
||||
User user,
|
||||
string token)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperOrganizationDuoUniversalTokenService(BuildDuoClient(), organization, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.RequestDuoValidationAsync(Arg.Any<Duo.Client>(), Arg.Any<IDataProtectorTokenFactory<DuoUserStateTokenable>>(), user, token)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(token, organization, user);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateAsync_ClientNull_ReturnsFalse(
|
||||
SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider,
|
||||
Organization organization,
|
||||
User user,
|
||||
string token)
|
||||
{
|
||||
// Arrange
|
||||
SetUpProperOrganizationDuoUniversalTokenService(null, organization, sutProvider);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.RequestDuoValidationAsync(Arg.Any<Duo.Client>(), Arg.Any<IDataProtectorTokenFactory<DuoUserStateTokenable>>(), user, token)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(token, organization, user);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the IDuoUniversalTokenService is properly setup for the test.
|
||||
/// This ensures that the private GetDuoClientAsync, and GetDuoTwoFactorProvider
|
||||
/// methods will return true enabling the test to execute on the correct path.
|
||||
///
|
||||
/// BitAutoData cannot create the Duo.Client since it does not have a public constructor
|
||||
/// so we have to use the ClientBUilder(), the client is not used meaningfully in the tests.
|
||||
/// </summary>
|
||||
/// <param name="user">user from calling test</param>
|
||||
/// <param name="sutProvider">self</param>
|
||||
private void SetUpProperOrganizationDuoUniversalTokenService(
|
||||
Duo.Client client, Organization organization, SutProvider<OrganizationDuoUniversalTokenProvider> sutProvider)
|
||||
{
|
||||
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderJson();
|
||||
organization.Enabled = true;
|
||||
organization.Use2fa = true;
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||
.BuildDuoTwoFactorClientAsync(Arg.Any<TwoFactorProvider>())
|
||||
.Returns(client);
|
||||
}
|
||||
|
||||
private Duo.Client BuildDuoClient()
|
||||
{
|
||||
var clientId = new string('c', 20);
|
||||
var clientSecret = new string('s', 40);
|
||||
return new Duo.ClientBuilder(clientId, clientSecret, "api-abcd1234.duosecurity.com", "redirectUrl").Build();
|
||||
}
|
||||
|
||||
private string GetTwoFactorOrganizationDuoProviderJson()
|
||||
{
|
||||
return
|
||||
"{\"6\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
|
||||
private string GetTwoFactorOrganizationDuoProviderNotEnabledJson()
|
||||
{
|
||||
return
|
||||
"{\"6\":{\"Enabled\":false,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
using Bit.Core.Auth.Identity.TokenProviders;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class DuoUniversalTokenServiceTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData("", "ClientId", "ClientSecret")]
|
||||
[BitAutoData("api-valid.duosecurity.com", "", "ClientSecret")]
|
||||
[BitAutoData("api-valid.duosecurity.com", "ClientId", "")]
|
||||
public async void ValidateDuoConfiguration_InvalidConfig_ReturnsFalse(
|
||||
string host, string clientId, string clientSecret, SutProvider<DuoUniversalTokenService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
/* AutoData handles arrangement */
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateDuoConfiguration(clientSecret, clientId, host);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(true, "api-valid.duosecurity.com")]
|
||||
[BitAutoData(false, "invalid")]
|
||||
[BitAutoData(false, "api-valid.duosecurity.com", null, "clientSecret")]
|
||||
[BitAutoData(false, "api-valid.duosecurity.com", "ClientId", null)]
|
||||
[BitAutoData(false, "api-valid.duosecurity.com", null, null)]
|
||||
public void HasProperDuoMetadata_ReturnMatchesExpected(
|
||||
bool expectedResponse, string host, string clientId,
|
||||
string clientSecret, SutProvider<DuoUniversalTokenService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var metaData = new Dictionary<string, object> { ["Host"] = host };
|
||||
|
||||
if (clientId != null)
|
||||
{
|
||||
metaData.Add("ClientId", clientId);
|
||||
}
|
||||
|
||||
if (clientSecret != null)
|
||||
{
|
||||
metaData.Add("ClientSecret", clientSecret);
|
||||
}
|
||||
|
||||
var provider = new TwoFactorProvider
|
||||
{
|
||||
MetaData = metaData
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = sutProvider.Sut.HasProperDuoMetadata(provider);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result, expectedResponse);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void HasProperDuoMetadata_ProviderIsNull_ReturnsFalse(
|
||||
SutProvider<DuoUniversalTokenService> sutProvider)
|
||||
{
|
||||
// Act
|
||||
var result = sutProvider.Sut.HasProperDuoMetadata(null);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData("api-valid.duosecurity.com", true)]
|
||||
[BitAutoData("api-valid.duofederal.com", true)]
|
||||
[BitAutoData("invalid", false)]
|
||||
public void ValidDuoHost_HostIsValid_ReturnTrue(
|
||||
string host, bool expectedResponse)
|
||||
{
|
||||
// Act
|
||||
var result = DuoUniversalTokenService.ValidDuoHost(host);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result, expectedResponse);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -24,6 +24,7 @@ public class LaunchDarklyFeatureServiceTests
|
||||
var currentContext = Substitute.For<ICurrentContext>();
|
||||
currentContext.UserId.Returns(Guid.NewGuid());
|
||||
currentContext.ClientVersion.Returns(new Version(AssemblyHelpers.GetVersion()));
|
||||
currentContext.ClientVersionIsPrerelease.Returns(true);
|
||||
currentContext.DeviceType.Returns(Enums.DeviceType.ChromeBrowser);
|
||||
|
||||
var client = Substitute.For<ILdClient>();
|
||||
|
@ -0,0 +1,149 @@
|
||||
using AutoFixture;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.ReportFeatures;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Core.Tools.Requests;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Tools.ReportFeatures;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class AddPasswordHealthReportApplicationCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_WithValidRequest_ShouldReturnPasswordHealthReportApplication(
|
||||
SutProvider<AddPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.Create<AddPasswordHealthReportApplicationRequest>();
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(fixture.Create<Organization>());
|
||||
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.CreateAsync(Arg.Any<PasswordHealthReportApplication>())
|
||||
.Returns(c => c.Arg<PasswordHealthReportApplication>());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.AddPasswordHealthReportApplicationAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_WithInvalidOrganizationId_ShouldThrowError(
|
||||
SutProvider<AddPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.Create<AddPasswordHealthReportApplicationRequest>();
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(null as Organization);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.AddPasswordHealthReportApplicationAsync(request));
|
||||
Assert.Equal("Invalid Organization", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_WithInvalidUrl_ShouldThrowError(
|
||||
SutProvider<AddPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.Build<AddPasswordHealthReportApplicationRequest>()
|
||||
.Without(_ => _.Url)
|
||||
.Create();
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(fixture.Create<Organization>());
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.AddPasswordHealthReportApplicationAsync(request));
|
||||
Assert.Equal("URL is required", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_Multiples_WithInvalidOrganizationId_ShouldThrowError(
|
||||
SutProvider<AddPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.Build<AddPasswordHealthReportApplicationRequest>()
|
||||
.Without(_ => _.OrganizationId)
|
||||
.CreateMany(2).ToList();
|
||||
|
||||
request[0].OrganizationId = Guid.NewGuid();
|
||||
request[1].OrganizationId = Guid.Empty;
|
||||
|
||||
// only return an organization for the first request and null for the second
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(Arg.Is<Guid>(x => x == request[0].OrganizationId))
|
||||
.Returns(fixture.Create<Organization>());
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.AddPasswordHealthReportApplicationAsync(request));
|
||||
Assert.Equal("Invalid Organization", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_Multiples_WithInvalidUrl_ShouldThrowError(
|
||||
SutProvider<AddPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.Build<AddPasswordHealthReportApplicationRequest>()
|
||||
.CreateMany(2).ToList();
|
||||
|
||||
request[1].Url = string.Empty;
|
||||
|
||||
// return an organization for both requests
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(fixture.Create<Organization>());
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.AddPasswordHealthReportApplicationAsync(request));
|
||||
Assert.Equal("URL is required", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_Multiples_WithValidRequest_ShouldBeSuccessful(
|
||||
SutProvider<AddPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.CreateMany<AddPasswordHealthReportApplicationRequest>(2);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(fixture.Create<Organization>());
|
||||
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.CreateAsync(Arg.Any<PasswordHealthReportApplication>())
|
||||
.Returns(c => c.Arg<PasswordHealthReportApplication>());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.AddPasswordHealthReportApplicationAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Count() == 2);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().Received(2);
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>().Received(2);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
using AutoFixture;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.ReportFeatures;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Tools.ReportFeatures;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class GetPasswordHealthReportApplicationQueryTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetPasswordHealthReportApplicationAsync_WithValidOrganizationId_ShouldReturnPasswordHealthReportApplication(
|
||||
SutProvider<GetPasswordHealthReportApplicationQuery> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var organizationId = fixture.Create<Guid>();
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.GetByOrganizationIdAsync(Arg.Any<Guid>())
|
||||
.Returns(fixture.CreateMany<PasswordHealthReportApplication>(2).ToList());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetPasswordHealthReportApplicationAsync(organizationId);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.Count() == 2);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetPasswordHealthReportApplicationAsync_WithInvalidOrganizationId_ShouldFail(
|
||||
SutProvider<GetPasswordHealthReportApplicationQuery> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.GetByOrganizationIdAsync(Arg.Is<Guid>(x => x == Guid.Empty))
|
||||
.Returns(new List<PasswordHealthReportApplication>());
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.GetPasswordHealthReportApplicationAsync(Guid.Empty));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("OrganizationId is required.", exception.Message);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user