mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 16:12:49 -05:00
Merge branch 'km/pm-10600' into km/pm-10600-full-notification-content
# Conflicts: # src/Core/Services/Implementations/MultiServicePushNotificationService.cs
This commit is contained in:
@ -1,6 +1,4 @@
|
||||
#nullable enable
|
||||
|
||||
using Bit.Admin.Models;
|
||||
using Bit.Admin.Models;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
@ -116,30 +114,26 @@ public class UserViewModelTests
|
||||
|
||||
var actual = UserViewModel.MapViewModel(user, true, Array.Empty<Cipher>(), verifiedDomain);
|
||||
|
||||
Assert.True(actual.DomainVerified);
|
||||
Assert.True(actual.ClaimedAccount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void MapUserViewModel_WithoutVerifiedDomain_ReturnsUserViewModel(User user)
|
||||
{
|
||||
|
||||
var verifiedDomain = false;
|
||||
|
||||
var actual = UserViewModel.MapViewModel(user, true, Array.Empty<Cipher>(), verifiedDomain);
|
||||
|
||||
Assert.False(actual.DomainVerified);
|
||||
Assert.False(actual.ClaimedAccount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void MapUserViewModel_WithNullVerifiedDomain_ReturnsUserViewModel(User user)
|
||||
{
|
||||
|
||||
var actual = UserViewModel.MapViewModel(user, true, Array.Empty<Cipher>(), null);
|
||||
|
||||
Assert.Null(actual.DomainVerified);
|
||||
Assert.Null(actual.ClaimedAccount);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,163 @@
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using Bit.Api.AdminConsole.Public.Models.Request;
|
||||
using Bit.Api.AdminConsole.Public.Models.Response;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Api.IntegrationTest.Helpers;
|
||||
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.Billing.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.AdminConsole.Public.Controllers;
|
||||
|
||||
public class PoliciesControllerTests : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly ApiApplicationFactory _factory;
|
||||
private readonly LoginHelper _loginHelper;
|
||||
|
||||
// These will get set in `InitializeAsync` which is ran before all tests
|
||||
private Organization _organization = null!;
|
||||
private string _ownerEmail = null!;
|
||||
|
||||
public PoliciesControllerTests(ApiApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_client = factory.CreateClient();
|
||||
_loginHelper = new LoginHelper(_factory, _client);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// Create the owner account
|
||||
_ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(_ownerEmail);
|
||||
|
||||
// Create the organization
|
||||
(_organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually2023,
|
||||
ownerEmail: _ownerEmail, passwordManagerSeats: 10, paymentMethod: PaymentMethodType.Card);
|
||||
|
||||
// Authorize with the organization api key
|
||||
await _loginHelper.LoginWithOrganizationApiKeyAsync(_organization.Id);
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_client.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Post_NewPolicy()
|
||||
{
|
||||
var policyType = PolicyType.MasterPassword;
|
||||
var request = new PolicyUpdateRequestModel
|
||||
{
|
||||
Enabled = true,
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
{ "minComplexity", 15},
|
||||
{ "requireLower", true}
|
||||
}
|
||||
};
|
||||
|
||||
var response = await _client.PutAsync($"/public/policies/{policyType}", JsonContent.Create(request));
|
||||
|
||||
// Assert against the response
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<PolicyResponseModel>();
|
||||
Assert.NotNull(result);
|
||||
|
||||
Assert.True(result.Enabled);
|
||||
Assert.Equal(policyType, result.Type);
|
||||
Assert.IsType<Guid>(result.Id);
|
||||
Assert.NotEqual(default, result.Id);
|
||||
Assert.NotNull(result.Data);
|
||||
Assert.Equal(15, ((JsonElement)result.Data["minComplexity"]).GetInt32());
|
||||
Assert.True(((JsonElement)result.Data["requireLower"]).GetBoolean());
|
||||
|
||||
// Assert against the database values
|
||||
var policyRepository = _factory.GetService<IPolicyRepository>();
|
||||
var policy = await policyRepository.GetByOrganizationIdTypeAsync(_organization.Id, policyType);
|
||||
Assert.NotNull(policy);
|
||||
|
||||
Assert.True(policy.Enabled);
|
||||
Assert.Equal(policyType, policy.Type);
|
||||
Assert.IsType<Guid>(policy.Id);
|
||||
Assert.NotEqual(default, policy.Id);
|
||||
Assert.Equal(_organization.Id, policy.OrganizationId);
|
||||
|
||||
Assert.NotNull(policy.Data);
|
||||
var data = policy.GetDataModel<MasterPasswordPolicyData>();
|
||||
var expectedData = new MasterPasswordPolicyData { MinComplexity = 15, RequireLower = true };
|
||||
AssertHelper.AssertPropertyEqual(expectedData, data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Post_UpdatePolicy()
|
||||
{
|
||||
var policyType = PolicyType.MasterPassword;
|
||||
var existingPolicy = new Policy
|
||||
{
|
||||
OrganizationId = _organization.Id,
|
||||
Enabled = true,
|
||||
Type = policyType
|
||||
};
|
||||
existingPolicy.SetDataModel(new MasterPasswordPolicyData
|
||||
{
|
||||
EnforceOnLogin = true,
|
||||
MinLength = 22,
|
||||
RequireSpecial = true
|
||||
});
|
||||
|
||||
var policyRepository = _factory.GetService<IPolicyRepository>();
|
||||
await policyRepository.UpsertAsync(existingPolicy);
|
||||
|
||||
// The Id isn't set until it's created in the database, get it back out to get the id
|
||||
var createdPolicy = await policyRepository.GetByOrganizationIdTypeAsync(_organization.Id, policyType);
|
||||
var expectedId = createdPolicy!.Id;
|
||||
|
||||
var request = new PolicyUpdateRequestModel
|
||||
{
|
||||
Enabled = false,
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
{ "minLength", 15},
|
||||
{ "requireUpper", true}
|
||||
}
|
||||
};
|
||||
|
||||
var response = await _client.PutAsync($"/public/policies/{policyType}", JsonContent.Create(request));
|
||||
|
||||
// Assert against the response
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<PolicyResponseModel>();
|
||||
Assert.NotNull(result);
|
||||
|
||||
Assert.False(result.Enabled);
|
||||
Assert.Equal(policyType, result.Type);
|
||||
Assert.Equal(expectedId, result.Id);
|
||||
Assert.NotNull(result.Data);
|
||||
Assert.Equal(15, ((JsonElement)result.Data["minLength"]).GetInt32());
|
||||
Assert.True(((JsonElement)result.Data["requireUpper"]).GetBoolean());
|
||||
|
||||
// Assert against the database values
|
||||
var policy = await policyRepository.GetByOrganizationIdTypeAsync(_organization.Id, policyType);
|
||||
Assert.NotNull(policy);
|
||||
|
||||
Assert.False(policy.Enabled);
|
||||
Assert.Equal(policyType, policy.Type);
|
||||
Assert.Equal(expectedId, policy.Id);
|
||||
Assert.Equal(_organization.Id, policy.OrganizationId);
|
||||
|
||||
Assert.NotNull(policy.Data);
|
||||
var data = policy.GetDataModel<MasterPasswordPolicyData>();
|
||||
Assert.Equal(15, data.MinLength);
|
||||
Assert.Equal(true, data.RequireUpper);
|
||||
}
|
||||
}
|
@ -16,6 +16,12 @@ public class LoginHelper
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public async Task LoginAsync(string email)
|
||||
{
|
||||
var tokens = await _factory.LoginAsync(email);
|
||||
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token);
|
||||
}
|
||||
|
||||
public async Task LoginWithOrganizationApiKeyAsync(Guid organizationId)
|
||||
{
|
||||
var (clientId, apiKey) = await GetOrganizationApiKey(_factory, organizationId);
|
||||
|
@ -1,13 +1,13 @@
|
||||
using System.Diagnostics;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.IntegrationTestCommon.Factories;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.Helpers;
|
||||
@ -24,11 +24,11 @@ public static class OrganizationTestHelpers
|
||||
PaymentMethodType paymentMethod = PaymentMethodType.None) where T : class
|
||||
{
|
||||
var userRepository = factory.GetService<IUserRepository>();
|
||||
var organizationService = factory.GetService<IOrganizationService>();
|
||||
var organizationSignUpCommand = factory.GetService<ICloudOrganizationSignUpCommand>();
|
||||
|
||||
var owner = await userRepository.GetByEmailAsync(ownerEmail);
|
||||
|
||||
var signUpResult = await organizationService.SignUpAsync(new OrganizationSignup
|
||||
var signUpResult = await organizationSignUpCommand.SignUpOrganizationAsync(new OrganizationSignup
|
||||
{
|
||||
Name = name,
|
||||
BillingEmail = billingEmail,
|
||||
@ -39,9 +39,9 @@ public static class OrganizationTestHelpers
|
||||
PaymentMethodType = paymentMethod
|
||||
});
|
||||
|
||||
Debug.Assert(signUpResult.organizationUser is not null);
|
||||
Debug.Assert(signUpResult.OrganizationUser is not null);
|
||||
|
||||
return new Tuple<Organization, OrganizationUser>(signUpResult.organization, signUpResult.organizationUser);
|
||||
return new Tuple<Organization, OrganizationUser>(signUpResult.Organization, signUpResult.OrganizationUser);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -0,0 +1,164 @@
|
||||
using System.Net;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Api.IntegrationTest.Helpers;
|
||||
using Bit.Api.KeyManagement.Models.Requests;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.KeyManagement.Controllers;
|
||||
|
||||
public class AccountsKeyManagementControllerTests : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
|
||||
{
|
||||
private static readonly string _mockEncryptedString =
|
||||
"2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk=";
|
||||
|
||||
private readonly HttpClient _client;
|
||||
private readonly IEmergencyAccessRepository _emergencyAccessRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly ApiApplicationFactory _factory;
|
||||
private readonly LoginHelper _loginHelper;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private string _ownerEmail = null!;
|
||||
|
||||
public AccountsKeyManagementControllerTests(ApiApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_factory.UpdateConfiguration("globalSettings:launchDarkly:flagValues:pm-12241-private-key-regeneration",
|
||||
"true");
|
||||
_client = factory.CreateClient();
|
||||
_loginHelper = new LoginHelper(_factory, _client);
|
||||
_userRepository = _factory.GetService<IUserRepository>();
|
||||
_emergencyAccessRepository = _factory.GetService<IEmergencyAccessRepository>();
|
||||
_organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(_ownerEmail);
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_client.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegenerateKeysAsync_FeatureFlagTurnedOff_NotFound(KeyRegenerationRequestModel request)
|
||||
{
|
||||
// Localize factory to inject a false value for the feature flag.
|
||||
var localFactory = new ApiApplicationFactory();
|
||||
localFactory.UpdateConfiguration("globalSettings:launchDarkly:flagValues:pm-12241-private-key-regeneration",
|
||||
"false");
|
||||
var localClient = localFactory.CreateClient();
|
||||
var localEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
var localLoginHelper = new LoginHelper(localFactory, localClient);
|
||||
await localFactory.LoginWithNewAccount(localEmail);
|
||||
await localLoginHelper.LoginAsync(localEmail);
|
||||
|
||||
request.UserKeyEncryptedUserPrivateKey = _mockEncryptedString;
|
||||
|
||||
var response = await localClient.PostAsJsonAsync("/accounts/key-management/regenerate-keys", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegenerateKeysAsync_NotLoggedIn_Unauthorized(KeyRegenerationRequestModel request)
|
||||
{
|
||||
request.UserKeyEncryptedUserPrivateKey = _mockEncryptedString;
|
||||
|
||||
var response = await _client.PostAsJsonAsync("/accounts/key-management/regenerate-keys", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserStatusType.Confirmed, EmergencyAccessStatusType.Confirmed)]
|
||||
[BitAutoData(OrganizationUserStatusType.Confirmed, EmergencyAccessStatusType.RecoveryApproved)]
|
||||
[BitAutoData(OrganizationUserStatusType.Confirmed, EmergencyAccessStatusType.RecoveryInitiated)]
|
||||
[BitAutoData(OrganizationUserStatusType.Revoked, EmergencyAccessStatusType.Confirmed)]
|
||||
[BitAutoData(OrganizationUserStatusType.Revoked, EmergencyAccessStatusType.RecoveryApproved)]
|
||||
[BitAutoData(OrganizationUserStatusType.Revoked, EmergencyAccessStatusType.RecoveryInitiated)]
|
||||
[BitAutoData(OrganizationUserStatusType.Confirmed, null)]
|
||||
[BitAutoData(OrganizationUserStatusType.Revoked, null)]
|
||||
[BitAutoData(OrganizationUserStatusType.Invited, EmergencyAccessStatusType.Confirmed)]
|
||||
[BitAutoData(OrganizationUserStatusType.Invited, EmergencyAccessStatusType.RecoveryApproved)]
|
||||
[BitAutoData(OrganizationUserStatusType.Invited, EmergencyAccessStatusType.RecoveryInitiated)]
|
||||
public async Task RegenerateKeysAsync_UserInOrgOrHasDesignatedEmergencyAccess_ThrowsBadRequest(
|
||||
OrganizationUserStatusType organizationUserStatus,
|
||||
EmergencyAccessStatusType? emergencyAccessStatus,
|
||||
KeyRegenerationRequestModel request)
|
||||
{
|
||||
if (organizationUserStatus is OrganizationUserStatusType.Confirmed or OrganizationUserStatusType.Revoked)
|
||||
{
|
||||
await CreateOrganizationUserAsync(organizationUserStatus);
|
||||
}
|
||||
|
||||
if (emergencyAccessStatus != null)
|
||||
{
|
||||
await CreateDesignatedEmergencyAccessAsync(emergencyAccessStatus.Value);
|
||||
}
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
request.UserKeyEncryptedUserPrivateKey = _mockEncryptedString;
|
||||
|
||||
var response = await _client.PostAsJsonAsync("/accounts/key-management/regenerate-keys", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegenerateKeysAsync_Success(KeyRegenerationRequestModel request)
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
request.UserKeyEncryptedUserPrivateKey = _mockEncryptedString;
|
||||
|
||||
var response = await _client.PostAsJsonAsync("/accounts/key-management/regenerate-keys", request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var user = await _userRepository.GetByEmailAsync(_ownerEmail);
|
||||
Assert.NotNull(user);
|
||||
Assert.Equal(request.UserPublicKey, user.PublicKey);
|
||||
Assert.Equal(request.UserKeyEncryptedUserPrivateKey, user.PrivateKey);
|
||||
}
|
||||
|
||||
private async Task CreateOrganizationUserAsync(OrganizationUserStatusType organizationUserStatus)
|
||||
{
|
||||
var (_, organizationUser) = await OrganizationTestHelpers.SignUpAsync(_factory,
|
||||
PlanType.EnterpriseAnnually, _ownerEmail, passwordManagerSeats: 10,
|
||||
paymentMethod: PaymentMethodType.Card);
|
||||
organizationUser.Status = organizationUserStatus;
|
||||
await _organizationUserRepository.ReplaceAsync(organizationUser);
|
||||
}
|
||||
|
||||
private async Task CreateDesignatedEmergencyAccessAsync(EmergencyAccessStatusType emergencyAccessStatus)
|
||||
{
|
||||
var tempEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(tempEmail);
|
||||
|
||||
var tempUser = await _userRepository.GetByEmailAsync(tempEmail);
|
||||
var user = await _userRepository.GetByEmailAsync(_ownerEmail);
|
||||
var emergencyAccess = new EmergencyAccess
|
||||
{
|
||||
GrantorId = tempUser!.Id,
|
||||
GranteeId = user!.Id,
|
||||
KeyEncrypted = _mockEncryptedString,
|
||||
Status = emergencyAccessStatus,
|
||||
Type = EmergencyAccessType.View,
|
||||
WaitTimeDays = 10,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
RevisionDate = DateTime.UtcNow
|
||||
};
|
||||
await _emergencyAccessRepository.CreateAsync(emergencyAccess);
|
||||
}
|
||||
}
|
@ -0,0 +1,577 @@
|
||||
using System.Net;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Api.IntegrationTest.Helpers;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.NotificationCenter.Models.Response;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.NotificationCenter.Entities;
|
||||
using Bit.Core.NotificationCenter.Enums;
|
||||
using Bit.Core.NotificationCenter.Repositories;
|
||||
using Bit.Core.Repositories;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.NotificationCenter.Controllers;
|
||||
|
||||
public class NotificationsControllerTests : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
|
||||
{
|
||||
private static readonly string _mockEncryptedBody =
|
||||
"2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk=";
|
||||
|
||||
private static readonly string _mockEncryptedTitle =
|
||||
"2.06CDSJjTZaigYHUuswIq5A==|trxgZl2RCkYrrmCvGE9WNA==|w5p05eI5wsaYeSyWtsAPvBX63vj798kIMxBTfSB0BQg=";
|
||||
|
||||
private static readonly Random _random = new();
|
||||
|
||||
private static TimeSpan OneMinuteTimeSpan => TimeSpan.FromMinutes(1);
|
||||
|
||||
private readonly HttpClient _client;
|
||||
private readonly ApiApplicationFactory _factory;
|
||||
private readonly LoginHelper _loginHelper;
|
||||
private readonly INotificationRepository _notificationRepository;
|
||||
private readonly INotificationStatusRepository _notificationStatusRepository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private Organization _organization = null!;
|
||||
private OrganizationUser _organizationUserOwner = null!;
|
||||
private string _ownerEmail = null!;
|
||||
private List<(Notification, NotificationStatus?)> _notificationsWithStatuses = null!;
|
||||
|
||||
public NotificationsControllerTests(ApiApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_client = factory.CreateClient();
|
||||
_loginHelper = new LoginHelper(_factory, _client);
|
||||
_notificationRepository = _factory.GetService<INotificationRepository>();
|
||||
_notificationStatusRepository = _factory.GetService<INotificationStatusRepository>();
|
||||
_userRepository = _factory.GetService<IUserRepository>();
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// Create the owner account
|
||||
_ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(_ownerEmail);
|
||||
|
||||
// Create the organization
|
||||
(_organization, _organizationUserOwner) = await OrganizationTestHelpers.SignUpAsync(_factory,
|
||||
plan: PlanType.EnterpriseAnnually, ownerEmail: _ownerEmail, passwordManagerSeats: 10,
|
||||
paymentMethod: PaymentMethodType.Card);
|
||||
|
||||
_notificationsWithStatuses = await CreateNotificationsWithStatusesAsync();
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_client.Dispose();
|
||||
|
||||
foreach (var (notification, _) in _notificationsWithStatuses)
|
||||
{
|
||||
_notificationRepository.DeleteAsync(notification);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("invalid")]
|
||||
[InlineData("-1")]
|
||||
[InlineData("0")]
|
||||
public async Task ListAsync_RequestValidationContinuationInvalidNumber_BadRequest(string continuationToken)
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var response = await _client.GetAsync($"/notifications?continuationToken={continuationToken}");
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<ErrorResponseModel>();
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("ContinuationToken", result.ValidationErrors);
|
||||
Assert.Contains("Continuation token must be a positive, non zero integer.",
|
||||
result.ValidationErrors["ContinuationToken"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListAsync_RequestValidationContinuationTokenMaxLengthExceeded_BadRequest()
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var response = await _client.GetAsync("/notifications?continuationToken=1234567890");
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<ErrorResponseModel>();
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("ContinuationToken", result.ValidationErrors);
|
||||
Assert.Contains("The field ContinuationToken must be a string with a maximum length of 9.",
|
||||
result.ValidationErrors["ContinuationToken"]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("9")]
|
||||
[InlineData("1001")]
|
||||
public async Task ListAsync_RequestValidationPageSizeInvalidRange_BadRequest(string pageSize)
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var response = await _client.GetAsync($"/notifications?pageSize={pageSize}");
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<ErrorResponseModel>();
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("PageSize", result.ValidationErrors);
|
||||
Assert.Contains("The field PageSize must be between 10 and 1000.",
|
||||
result.ValidationErrors["PageSize"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListAsync_NotLoggedIn_Unauthorized()
|
||||
{
|
||||
var response = await _client.GetAsync("/notifications");
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, null, "2", 10)]
|
||||
[InlineData(10, null, "2", 10)]
|
||||
[InlineData(10, 2, "3", 10)]
|
||||
[InlineData(10, 3, null, 4)]
|
||||
[InlineData(24, null, "2", 24)]
|
||||
[InlineData(24, 2, null, 0)]
|
||||
[InlineData(1000, null, null, 24)]
|
||||
public async Task ListAsync_PaginationFilter_ReturnsNextPageOfNotificationsCorrectOrder(
|
||||
int? pageSize, int? pageNumber, string? expectedContinuationToken, int expectedCount)
|
||||
{
|
||||
var pageSizeWithDefault = pageSize ?? 10;
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var skip = pageNumber == null ? 0 : (pageNumber.Value - 1) * pageSizeWithDefault;
|
||||
|
||||
var notificationsInOrder = _notificationsWithStatuses.OrderByDescending(e => e.Item1.Priority)
|
||||
.ThenByDescending(e => e.Item1.CreationDate)
|
||||
.Skip(skip)
|
||||
.Take(pageSizeWithDefault)
|
||||
.ToList();
|
||||
|
||||
var url = "/notifications";
|
||||
if (pageNumber != null)
|
||||
{
|
||||
url += $"?continuationToken={pageNumber}";
|
||||
}
|
||||
|
||||
if (pageSize != null)
|
||||
{
|
||||
url += url.Contains('?') ? "&" : "?";
|
||||
url += $"pageSize={pageSize}";
|
||||
}
|
||||
|
||||
var response = await _client.GetAsync(url);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<ListResponseModel<NotificationResponseModel>>();
|
||||
Assert.NotNull(result?.Data);
|
||||
Assert.InRange(result.Data.Count(), 0, pageSizeWithDefault);
|
||||
Assert.Equal(expectedCount, notificationsInOrder.Count);
|
||||
Assert.Equal(notificationsInOrder.Count, result.Data.Count());
|
||||
AssertNotificationResponseModels(result.Data, notificationsInOrder);
|
||||
|
||||
Assert.Equal(expectedContinuationToken, result.ContinuationToken);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, null)]
|
||||
[InlineData(null, false)]
|
||||
[InlineData(null, true)]
|
||||
[InlineData(false, null)]
|
||||
[InlineData(true, null)]
|
||||
[InlineData(false, false)]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(true, true)]
|
||||
public async Task ListAsync_ReadStatusDeletedStatusFilter_ReturnsFilteredNotificationsCorrectOrder(
|
||||
bool? readStatusFilter, bool? deletedStatusFilter)
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
var notificationsInOrder = _notificationsWithStatuses.FindAll(e =>
|
||||
(readStatusFilter == null || readStatusFilter == (e.Item2?.ReadDate != null)) &&
|
||||
(deletedStatusFilter == null || deletedStatusFilter == (e.Item2?.DeletedDate != null)))
|
||||
.OrderByDescending(e => e.Item1.Priority)
|
||||
.ThenByDescending(e => e.Item1.CreationDate)
|
||||
.Take(10)
|
||||
.ToList();
|
||||
|
||||
var url = "/notifications";
|
||||
if (readStatusFilter != null)
|
||||
{
|
||||
url += $"?readStatusFilter={readStatusFilter}";
|
||||
}
|
||||
|
||||
if (deletedStatusFilter != null)
|
||||
{
|
||||
url += url.Contains('?') ? "&" : "?";
|
||||
url += $"deletedStatusFilter={deletedStatusFilter}";
|
||||
}
|
||||
|
||||
var response = await _client.GetAsync(url);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<ListResponseModel<NotificationResponseModel>>();
|
||||
Assert.NotNull(result?.Data);
|
||||
Assert.InRange(result.Data.Count(), 0, 10);
|
||||
Assert.Equal(notificationsInOrder.Count, result.Data.Count());
|
||||
AssertNotificationResponseModels(result.Data, notificationsInOrder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsDeletedAsync_NotLoggedIn_Unauthorized()
|
||||
{
|
||||
var url = $"/notifications/{Guid.NewGuid().ToString()}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsDeletedAsync_NonExistentNotificationId_NotFound()
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{Guid.NewGuid()}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsDeletedAsync_UserIdNotMatching_NotFound()
|
||||
{
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
var user = (await _userRepository.GetByEmailAsync(email))!;
|
||||
var notifications = await CreateNotificationsAsync(user.Id);
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsDeletedAsync_OrganizationIdNotMatchingUserNotPartOfOrganization_NotFound()
|
||||
{
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
var user = (await _userRepository.GetByEmailAsync(email))!;
|
||||
var notifications = await CreateNotificationsAsync(user.Id, _organization.Id);
|
||||
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsDeletedAsync_OrganizationIdNotMatchingUserPartOfDifferentOrganization_NotFound()
|
||||
{
|
||||
var (organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory,
|
||||
plan: PlanType.EnterpriseAnnually, ownerEmail: _ownerEmail, passwordManagerSeats: 10,
|
||||
paymentMethod: PaymentMethodType.Card);
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
var user = (await _userRepository.GetByEmailAsync(email))!;
|
||||
await OrganizationTestHelpers.CreateUserAsync(_factory, organization.Id, email, OrganizationUserType.User);
|
||||
var notifications = await CreateNotificationsAsync(user.Id, _organization.Id);
|
||||
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsDeletedAsync_NotificationStatusNotExisting_Created()
|
||||
{
|
||||
var notifications = await CreateNotificationsAsync(_organizationUserOwner.UserId);
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var notificationStatus = await _notificationStatusRepository.GetByNotificationIdAndUserIdAsync(
|
||||
notifications[0].Id, _organizationUserOwner.UserId!.Value);
|
||||
Assert.NotNull(notificationStatus);
|
||||
Assert.NotNull(notificationStatus.DeletedDate);
|
||||
Assert.Equal(DateTime.UtcNow, notificationStatus.DeletedDate.Value, OneMinuteTimeSpan);
|
||||
Assert.Null(notificationStatus.ReadDate);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
private async void MarkAsDeletedAsync_NotificationStatusExisting_Updated(bool deletedDateNull)
|
||||
{
|
||||
var notifications = await CreateNotificationsAsync(_organizationUserOwner.UserId);
|
||||
await _notificationStatusRepository.CreateAsync(new NotificationStatus
|
||||
{
|
||||
NotificationId = notifications[0].Id,
|
||||
UserId = _organizationUserOwner.UserId!.Value,
|
||||
ReadDate = null,
|
||||
DeletedDate = deletedDateNull ? null : DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600))
|
||||
});
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var notificationStatus = await _notificationStatusRepository.GetByNotificationIdAndUserIdAsync(
|
||||
notifications[0].Id, _organizationUserOwner.UserId!.Value);
|
||||
Assert.NotNull(notificationStatus);
|
||||
Assert.NotNull(notificationStatus.DeletedDate);
|
||||
Assert.Equal(DateTime.UtcNow, notificationStatus.DeletedDate.Value, OneMinuteTimeSpan);
|
||||
Assert.Null(notificationStatus.ReadDate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsReadAsync_NotLoggedIn_Unauthorized()
|
||||
{
|
||||
var url = $"/notifications/{Guid.NewGuid().ToString()}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsReadAsync_NonExistentNotificationId_NotFound()
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{Guid.NewGuid()}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsReadAsync_UserIdNotMatching_NotFound()
|
||||
{
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
var user = (await _userRepository.GetByEmailAsync(email))!;
|
||||
var notifications = await CreateNotificationsAsync(user.Id);
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsReadAsync_OrganizationIdNotMatchingUserNotPartOfOrganization_NotFound()
|
||||
{
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
var user = (await _userRepository.GetByEmailAsync(email))!;
|
||||
var notifications = await CreateNotificationsAsync(user.Id, _organization.Id);
|
||||
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsReadAsync_OrganizationIdNotMatchingUserPartOfDifferentOrganization_NotFound()
|
||||
{
|
||||
var (organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory,
|
||||
plan: PlanType.EnterpriseAnnually, ownerEmail: _ownerEmail, passwordManagerSeats: 10,
|
||||
paymentMethod: PaymentMethodType.Card);
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
var user = (await _userRepository.GetByEmailAsync(email))!;
|
||||
await OrganizationTestHelpers.CreateUserAsync(_factory, organization.Id, email, OrganizationUserType.User);
|
||||
var notifications = await CreateNotificationsAsync(user.Id, _organization.Id);
|
||||
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsReadAsync_NotificationStatusNotExisting_Created()
|
||||
{
|
||||
var notifications = await CreateNotificationsAsync(_organizationUserOwner.UserId);
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var notificationStatus = await _notificationStatusRepository.GetByNotificationIdAndUserIdAsync(
|
||||
notifications[0].Id, _organizationUserOwner.UserId!.Value);
|
||||
Assert.NotNull(notificationStatus);
|
||||
Assert.NotNull(notificationStatus.ReadDate);
|
||||
Assert.Equal(DateTime.UtcNow, notificationStatus.ReadDate.Value, OneMinuteTimeSpan);
|
||||
Assert.Null(notificationStatus.DeletedDate);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
private async void MarkAsReadAsync_NotificationStatusExisting_Updated(bool readDateNull)
|
||||
{
|
||||
var notifications = await CreateNotificationsAsync(_organizationUserOwner.UserId);
|
||||
await _notificationStatusRepository.CreateAsync(new NotificationStatus
|
||||
{
|
||||
NotificationId = notifications[0].Id,
|
||||
UserId = _organizationUserOwner.UserId!.Value,
|
||||
ReadDate = readDateNull ? null : DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600)),
|
||||
DeletedDate = null
|
||||
});
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var notificationStatus = await _notificationStatusRepository.GetByNotificationIdAndUserIdAsync(
|
||||
notifications[0].Id, _organizationUserOwner.UserId!.Value);
|
||||
Assert.NotNull(notificationStatus);
|
||||
Assert.NotNull(notificationStatus.ReadDate);
|
||||
Assert.Equal(DateTime.UtcNow, notificationStatus.ReadDate.Value, OneMinuteTimeSpan);
|
||||
Assert.Null(notificationStatus.DeletedDate);
|
||||
}
|
||||
|
||||
private static void AssertNotificationResponseModels(
|
||||
IEnumerable<NotificationResponseModel> notificationResponseModels,
|
||||
List<(Notification, NotificationStatus?)> expectedNotificationsWithStatuses)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var notificationResponseModel in notificationResponseModels)
|
||||
{
|
||||
Assert.Contains(expectedNotificationsWithStatuses, e => e.Item1.Id == notificationResponseModel.Id);
|
||||
var (expectedNotification, expectedNotificationStatus) = expectedNotificationsWithStatuses[i];
|
||||
Assert.NotNull(expectedNotification);
|
||||
Assert.Equal(expectedNotification.Priority, notificationResponseModel.Priority);
|
||||
Assert.Equal(expectedNotification.Title, notificationResponseModel.Title);
|
||||
Assert.Equal(expectedNotification.Body, notificationResponseModel.Body);
|
||||
Assert.Equal(expectedNotification.RevisionDate, notificationResponseModel.Date);
|
||||
if (expectedNotificationStatus != null)
|
||||
{
|
||||
Assert.Equal(expectedNotificationStatus.ReadDate, notificationResponseModel.ReadDate);
|
||||
Assert.Equal(expectedNotificationStatus.DeletedDate, notificationResponseModel.DeletedDate);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Null(notificationResponseModel.ReadDate);
|
||||
Assert.Null(notificationResponseModel.DeletedDate);
|
||||
}
|
||||
|
||||
Assert.Equal("notification", notificationResponseModel.Object);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<(Notification, NotificationStatus?)>> CreateNotificationsWithStatusesAsync()
|
||||
{
|
||||
var userId = (Guid)_organizationUserOwner.UserId!;
|
||||
|
||||
var globalNotifications = await CreateNotificationsAsync();
|
||||
var userWithoutOrganizationNotifications = await CreateNotificationsAsync(userId: userId);
|
||||
var organizationWithoutUserNotifications = await CreateNotificationsAsync(organizationId: _organization.Id);
|
||||
var userPartOrOrganizationNotifications = await CreateNotificationsAsync(userId: userId,
|
||||
organizationId: _organization.Id);
|
||||
|
||||
var globalNotificationWithStatuses = await CreateNotificationStatusesAsync(globalNotifications, userId);
|
||||
var userWithoutOrganizationNotificationWithStatuses =
|
||||
await CreateNotificationStatusesAsync(userWithoutOrganizationNotifications, userId);
|
||||
var organizationWithoutUserNotificationWithStatuses =
|
||||
await CreateNotificationStatusesAsync(organizationWithoutUserNotifications, userId);
|
||||
var userPartOrOrganizationNotificationWithStatuses =
|
||||
await CreateNotificationStatusesAsync(userPartOrOrganizationNotifications, userId);
|
||||
|
||||
return new List<List<(Notification, NotificationStatus?)>>
|
||||
{
|
||||
globalNotificationWithStatuses,
|
||||
userWithoutOrganizationNotificationWithStatuses,
|
||||
organizationWithoutUserNotificationWithStatuses,
|
||||
userPartOrOrganizationNotificationWithStatuses
|
||||
}
|
||||
.SelectMany(n => n)
|
||||
.Where(n => n.Item1.ClientType is ClientType.All or ClientType.Web)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private async Task<List<Notification>> CreateNotificationsAsync(Guid? userId = null, Guid? organizationId = null,
|
||||
int numberToCreate = 3)
|
||||
{
|
||||
var priorities = Enum.GetValues<Priority>();
|
||||
var clientTypes = Enum.GetValues<ClientType>();
|
||||
|
||||
var notifications = new List<Notification>();
|
||||
|
||||
foreach (var clientType in clientTypes)
|
||||
{
|
||||
for (var i = 0; i < numberToCreate; i++)
|
||||
{
|
||||
var notification = new Notification
|
||||
{
|
||||
Global = userId == null && organizationId == null,
|
||||
UserId = userId,
|
||||
OrganizationId = organizationId,
|
||||
Title = _mockEncryptedTitle,
|
||||
Body = _mockEncryptedBody,
|
||||
Priority = (Priority)priorities.GetValue(_random.Next(priorities.Length))!,
|
||||
ClientType = clientType,
|
||||
CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600)),
|
||||
RevisionDate = DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600))
|
||||
};
|
||||
|
||||
notification = await _notificationRepository.CreateAsync(notification);
|
||||
|
||||
notifications.Add(notification);
|
||||
}
|
||||
}
|
||||
|
||||
return notifications;
|
||||
}
|
||||
|
||||
private async Task<List<(Notification, NotificationStatus?)>> CreateNotificationStatusesAsync(
|
||||
List<Notification> notifications, Guid userId)
|
||||
{
|
||||
var readDateNotificationStatus = await _notificationStatusRepository.CreateAsync(new NotificationStatus
|
||||
{
|
||||
NotificationId = notifications[0].Id,
|
||||
UserId = userId,
|
||||
ReadDate = DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600)),
|
||||
DeletedDate = null
|
||||
});
|
||||
|
||||
var deletedDateNotificationStatus = await _notificationStatusRepository.CreateAsync(new NotificationStatus
|
||||
{
|
||||
NotificationId = notifications[1].Id,
|
||||
UserId = userId,
|
||||
ReadDate = null,
|
||||
DeletedDate = DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600))
|
||||
});
|
||||
|
||||
var readDateAndDeletedDateNotificationStatus = await _notificationStatusRepository.CreateAsync(
|
||||
new NotificationStatus
|
||||
{
|
||||
NotificationId = notifications[2].Id,
|
||||
UserId = userId,
|
||||
ReadDate = DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600)),
|
||||
DeletedDate = DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600))
|
||||
});
|
||||
|
||||
List<NotificationStatus> statuses =
|
||||
[readDateNotificationStatus, deletedDateNotificationStatus, readDateAndDeletedDateNotificationStatus];
|
||||
|
||||
return notifications.Select(n => (n, statuses.Find(s => s.NotificationId == n.Id))).ToList();
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Entities;
|
||||
@ -46,11 +47,11 @@ public class OrganizationsControllerTests : IDisposable
|
||||
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
||||
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IProviderBillingService _providerBillingService;
|
||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||
private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand;
|
||||
private readonly OrganizationsController _sut;
|
||||
|
||||
public OrganizationsControllerTests()
|
||||
@ -69,11 +70,11 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_userService = Substitute.For<IUserService>();
|
||||
_createOrganizationApiKeyCommand = Substitute.For<ICreateOrganizationApiKeyCommand>();
|
||||
_featureService = Substitute.For<IFeatureService>();
|
||||
_pushNotificationService = Substitute.For<IPushNotificationService>();
|
||||
_providerRepository = Substitute.For<IProviderRepository>();
|
||||
_providerBillingService = Substitute.For<IProviderBillingService>();
|
||||
_orgDeleteTokenDataFactory = Substitute.For<IDataProtectorTokenFactory<OrgDeleteTokenable>>();
|
||||
_removeOrganizationUserCommand = Substitute.For<IRemoveOrganizationUserCommand>();
|
||||
_cloudOrganizationSignUpCommand = Substitute.For<ICloudOrganizationSignUpCommand>();
|
||||
|
||||
_sut = new OrganizationsController(
|
||||
_organizationRepository,
|
||||
@ -90,11 +91,11 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_organizationApiKeyRepository,
|
||||
_featureService,
|
||||
_globalSettings,
|
||||
_pushNotificationService,
|
||||
_providerRepository,
|
||||
_providerBillingService,
|
||||
_orgDeleteTokenDataFactory,
|
||||
_removeOrganizationUserCommand);
|
||||
_removeOrganizationUserCommand,
|
||||
_cloudOrganizationSignUpCommand);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -129,7 +130,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
Assert.Contains("Your organization's Single Sign-On settings prevent you from leaving.",
|
||||
exception.Message);
|
||||
|
||||
await _removeOrganizationUserCommand.DidNotReceiveWithAnyArgs().RemoveUserAsync(default, default);
|
||||
await _removeOrganizationUserCommand.DidNotReceiveWithAnyArgs().UserLeaveAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
@ -192,7 +193,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
|
||||
await _sut.Leave(orgId);
|
||||
|
||||
await _removeOrganizationUserCommand.Received(1).RemoveUserAsync(orgId, user.Id);
|
||||
await _removeOrganizationUserCommand.Received(1).UserLeaveAsync(orgId, user.Id);
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
|
@ -1,33 +0,0 @@
|
||||
using Bit.Api.AdminConsole.Public.Controllers;
|
||||
using Bit.Api.AdminConsole.Public.Models.Request;
|
||||
using Bit.Api.AdminConsole.Public.Models.Response;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.AdminConsole.Public.Controllers;
|
||||
|
||||
[ControllerCustomize(typeof(PoliciesController))]
|
||||
[SutProviderCustomize]
|
||||
public class PoliciesControllerTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[BitAutoData(PolicyType.SendOptions)]
|
||||
public async Task Put_NewPolicy_AppliesCorrectType(PolicyType type, Organization organization, PolicyUpdateRequestModel model, SutProvider<PoliciesController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationId.Returns(organization.Id);
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetByOrganizationIdTypeAsync(organization.Id, type).Returns((Policy)null);
|
||||
|
||||
var response = await sutProvider.Sut.Put(type, model) as JsonResult;
|
||||
var responseValue = response.Value as PolicyResponseModel;
|
||||
|
||||
Assert.Equal(type, responseValue.Type);
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ using Bit.Api.Auth.Controllers;
|
||||
using Bit.Api.Auth.Models.Request;
|
||||
using Bit.Api.Auth.Models.Request.Accounts;
|
||||
using Bit.Api.Auth.Models.Request.WebAuthn;
|
||||
using Bit.Api.Auth.Validators;
|
||||
using Bit.Api.KeyManagement.Validators;
|
||||
using Bit.Api.Tools.Models.Request;
|
||||
using Bit.Api.Vault.Models.Request;
|
||||
using Bit.Core;
|
||||
@ -14,12 +14,12 @@ using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
|
||||
using Bit.Core.Auth.UserFeatures.UserKey;
|
||||
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.UserKey;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
|
@ -10,6 +10,7 @@ using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Billing.Repositories;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
@ -47,6 +48,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly ISubscriberService _subscriberService;
|
||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||
private readonly IOrganizationInstallationRepository _organizationInstallationRepository;
|
||||
|
||||
private readonly OrganizationsController _sut;
|
||||
|
||||
@ -70,6 +72,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_referenceEventService = Substitute.For<IReferenceEventService>();
|
||||
_subscriberService = Substitute.For<ISubscriberService>();
|
||||
_removeOrganizationUserCommand = Substitute.For<IRemoveOrganizationUserCommand>();
|
||||
_organizationInstallationRepository = Substitute.For<IOrganizationInstallationRepository>();
|
||||
|
||||
_sut = new OrganizationsController(
|
||||
_organizationRepository,
|
||||
@ -85,7 +88,8 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_upgradeOrganizationPlanCommand,
|
||||
_addSecretsManagerSubscriptionCommand,
|
||||
_referenceEventService,
|
||||
_subscriberService);
|
||||
_subscriberService,
|
||||
_organizationInstallationRepository);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -6,11 +6,13 @@ 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.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@ -28,10 +30,19 @@ public class PoliciesControllerTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetMasterPasswordPolicy_WhenCalled_ReturnsMasterPasswordPolicy(
|
||||
SutProvider<PoliciesController> sutProvider, Guid orgId, Guid userId, OrganizationUser orgUser,
|
||||
Policy policy, MasterPasswordPolicyData mpPolicyData)
|
||||
SutProvider<PoliciesController> sutProvider,
|
||||
Guid orgId, Guid userId,
|
||||
OrganizationUser orgUser,
|
||||
Policy policy,
|
||||
MasterPasswordPolicyData mpPolicyData,
|
||||
Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
organization.UsePolicies = true;
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
organizationRepository.GetByIdAsync(orgId).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.GetProperUserId(Arg.Any<ClaimsPrincipal>())
|
||||
.Returns((Guid?)userId);
|
||||
@ -135,6 +146,39 @@ public class PoliciesControllerTests
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetMasterPasswordPolicy_WhenUsePoliciesIsFalse_ThrowsNotFoundException(
|
||||
SutProvider<PoliciesController> sutProvider,
|
||||
Guid orgId)
|
||||
{
|
||||
// Arrange
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
organizationRepository.GetByIdAsync(orgId).Returns((Organization)null);
|
||||
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetMasterPasswordPolicy_WhenOrgIsNull_ThrowsNotFoundException(
|
||||
SutProvider<PoliciesController> sutProvider,
|
||||
Guid orgId,
|
||||
Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
organization.UsePolicies = false;
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
organizationRepository.GetByIdAsync(orgId).Returns(organization);
|
||||
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task Get_WhenUserCanManagePolicies_WithExistingType_ReturnsExistingPolicy(
|
||||
@ -142,16 +186,16 @@ public class PoliciesControllerTests
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ManagePolicies(orgId)
|
||||
.Returns(true);
|
||||
.ManagePolicies(orgId)
|
||||
.Returns(true);
|
||||
|
||||
policy.Type = (PolicyType)type;
|
||||
policy.Enabled = true;
|
||||
policy.Data = null;
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(orgId, (PolicyType)type)
|
||||
.Returns(policy);
|
||||
.GetByOrganizationIdTypeAsync(orgId, (PolicyType)type)
|
||||
.Returns(policy);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.Get(orgId, type);
|
||||
@ -171,12 +215,12 @@ public class PoliciesControllerTests
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ManagePolicies(orgId)
|
||||
.Returns(true);
|
||||
.ManagePolicies(orgId)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(orgId, (PolicyType)type)
|
||||
.Returns((Policy)null);
|
||||
.GetByOrganizationIdTypeAsync(orgId, (PolicyType)type)
|
||||
.Returns((Policy)null);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.Get(orgId, type);
|
||||
@ -194,11 +238,221 @@ public class PoliciesControllerTests
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.ManagePolicies(orgId)
|
||||
.Returns(false);
|
||||
.ManagePolicies(orgId)
|
||||
.Returns(false);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.Get(orgId, type));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetByToken_WhenOrganizationUseUsePoliciesIsFalse_ThrowsNotFoundException(
|
||||
SutProvider<PoliciesController> sutProvider, Guid orgId, Guid organizationUserId, string token, string email,
|
||||
Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
organization.UsePolicies = false;
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
organizationRepository.GetByIdAsync(orgId).Returns(organization);
|
||||
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetByToken_WhenOrganizationIsNull_ThrowsNotFoundException(
|
||||
SutProvider<PoliciesController> sutProvider, Guid orgId, Guid organizationUserId, string token, string email)
|
||||
{
|
||||
// Arrange
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
organizationRepository.GetByIdAsync(orgId).Returns((Organization)null);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetByToken_WhenTokenIsInvalid_ThrowsNotFoundException(
|
||||
SutProvider<PoliciesController> sutProvider,
|
||||
Guid orgId,
|
||||
Guid organizationUserId,
|
||||
string token,
|
||||
string email,
|
||||
Organization organization
|
||||
)
|
||||
{
|
||||
// Arrange
|
||||
organization.UsePolicies = true;
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
organizationRepository.GetByIdAsync(orgId).Returns(organization);
|
||||
|
||||
var decryptedToken = Substitute.For<OrgUserInviteTokenable>();
|
||||
decryptedToken.Valid.Returns(false);
|
||||
|
||||
var orgUserInviteTokenDataFactory = sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>();
|
||||
|
||||
orgUserInviteTokenDataFactory.TryUnprotect(token, out Arg.Any<OrgUserInviteTokenable>())
|
||||
.Returns(x =>
|
||||
{
|
||||
x[1] = decryptedToken;
|
||||
return true;
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetByToken_WhenUserIsNull_ThrowsNotFoundException(
|
||||
SutProvider<PoliciesController> sutProvider,
|
||||
Guid orgId,
|
||||
Guid organizationUserId,
|
||||
string token,
|
||||
string email,
|
||||
Organization organization
|
||||
)
|
||||
{
|
||||
// Arrange
|
||||
organization.UsePolicies = true;
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
organizationRepository.GetByIdAsync(orgId).Returns(organization);
|
||||
|
||||
var decryptedToken = Substitute.For<OrgUserInviteTokenable>();
|
||||
decryptedToken.Valid.Returns(true);
|
||||
decryptedToken.OrgUserId = organizationUserId;
|
||||
decryptedToken.OrgUserEmail = email;
|
||||
|
||||
var orgUserInviteTokenDataFactory = sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>();
|
||||
|
||||
orgUserInviteTokenDataFactory.TryUnprotect(token, out Arg.Any<OrgUserInviteTokenable>())
|
||||
.Returns(x =>
|
||||
{
|
||||
x[1] = decryptedToken;
|
||||
return true;
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(organizationUserId)
|
||||
.Returns((OrganizationUser)null);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetByToken_WhenUserOrgIdDoesNotMatchOrgId_ThrowsNotFoundException(
|
||||
SutProvider<PoliciesController> sutProvider,
|
||||
Guid orgId,
|
||||
Guid organizationUserId,
|
||||
string token,
|
||||
string email,
|
||||
OrganizationUser orgUser,
|
||||
Organization organization
|
||||
)
|
||||
{
|
||||
// Arrange
|
||||
organization.UsePolicies = true;
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
organizationRepository.GetByIdAsync(orgId).Returns(organization);
|
||||
|
||||
var decryptedToken = Substitute.For<OrgUserInviteTokenable>();
|
||||
decryptedToken.Valid.Returns(true);
|
||||
decryptedToken.OrgUserId = organizationUserId;
|
||||
decryptedToken.OrgUserEmail = email;
|
||||
|
||||
var orgUserInviteTokenDataFactory = sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>();
|
||||
|
||||
orgUserInviteTokenDataFactory.TryUnprotect(token, out Arg.Any<OrgUserInviteTokenable>())
|
||||
.Returns(x =>
|
||||
{
|
||||
x[1] = decryptedToken;
|
||||
return true;
|
||||
});
|
||||
|
||||
orgUser.OrganizationId = Guid.Empty;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(organizationUserId)
|
||||
.Returns(orgUser);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetByToken_ShouldReturnEnabledPolicies(
|
||||
SutProvider<PoliciesController> sutProvider,
|
||||
Guid orgId,
|
||||
Guid organizationUserId,
|
||||
string token,
|
||||
string email,
|
||||
OrganizationUser orgUser,
|
||||
Organization organization
|
||||
)
|
||||
{
|
||||
// Arrange
|
||||
organization.UsePolicies = true;
|
||||
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
organizationRepository.GetByIdAsync(orgId).Returns(organization);
|
||||
|
||||
var decryptedToken = Substitute.For<OrgUserInviteTokenable>();
|
||||
decryptedToken.Valid.Returns(true);
|
||||
decryptedToken.OrgUserId = organizationUserId;
|
||||
decryptedToken.OrgUserEmail = email;
|
||||
|
||||
var orgUserInviteTokenDataFactory = sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>();
|
||||
|
||||
orgUserInviteTokenDataFactory.TryUnprotect(token, out Arg.Any<OrgUserInviteTokenable>())
|
||||
.Returns(x =>
|
||||
{
|
||||
x[1] = decryptedToken;
|
||||
return true;
|
||||
});
|
||||
|
||||
orgUser.OrganizationId = orgId;
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(organizationUserId)
|
||||
.Returns(orgUser);
|
||||
|
||||
var enabledPolicy = Substitute.For<Policy>();
|
||||
enabledPolicy.Enabled = true;
|
||||
var disabledPolicy = Substitute.For<Policy>();
|
||||
disabledPolicy.Enabled = false;
|
||||
|
||||
var policies = new[] { enabledPolicy, disabledPolicy };
|
||||
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetManyByOrganizationIdAsync(orgId)
|
||||
.Returns(policies);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId);
|
||||
|
||||
// Assert
|
||||
var expectedPolicy = result.Data.Single();
|
||||
|
||||
Assert.NotNull(result);
|
||||
|
||||
Assert.Equal(enabledPolicy.Id, expectedPolicy.Id);
|
||||
Assert.Equal(enabledPolicy.Type, expectedPolicy.Type);
|
||||
Assert.Equal(enabledPolicy.Enabled, expectedPolicy.Enabled);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,96 @@
|
||||
#nullable enable
|
||||
using System.Security.Claims;
|
||||
using Bit.Api.KeyManagement.Controllers;
|
||||
using Bit.Api.KeyManagement.Models.Requests;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.Commands.Interfaces;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.KeyManagement.Controllers;
|
||||
|
||||
[ControllerCustomize(typeof(AccountsKeyManagementController))]
|
||||
[SutProviderCustomize]
|
||||
[JsonDocumentCustomize]
|
||||
public class AccountsKeyManagementControllerTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegenerateKeysAsync_FeatureFlagOff_Throws(
|
||||
SutProvider<AccountsKeyManagementController> sutProvider,
|
||||
KeyRegenerationRequestModel data)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(Arg.Is(FeatureFlagKeys.PrivateKeyRegeneration))
|
||||
.Returns(false);
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsNull();
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.RegenerateKeysAsync(data));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().ReceivedWithAnyArgs(0)
|
||||
.GetManyByUserAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>().ReceivedWithAnyArgs(0)
|
||||
.GetManyDetailsByGranteeIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IRegenerateUserAsymmetricKeysCommand>().ReceivedWithAnyArgs(0)
|
||||
.RegenerateKeysAsync(Arg.Any<UserAsymmetricKeys>(),
|
||||
Arg.Any<ICollection<OrganizationUser>>(),
|
||||
Arg.Any<ICollection<EmergencyAccessDetails>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegenerateKeysAsync_UserNull_Throws(SutProvider<AccountsKeyManagementController> sutProvider,
|
||||
KeyRegenerationRequestModel data)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(Arg.Is(FeatureFlagKeys.PrivateKeyRegeneration))
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsNull();
|
||||
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.RegenerateKeysAsync(data));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().ReceivedWithAnyArgs(0)
|
||||
.GetManyByUserAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>().ReceivedWithAnyArgs(0)
|
||||
.GetManyDetailsByGranteeIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IRegenerateUserAsymmetricKeysCommand>().ReceivedWithAnyArgs(0)
|
||||
.RegenerateKeysAsync(Arg.Any<UserAsymmetricKeys>(),
|
||||
Arg.Any<ICollection<OrganizationUser>>(),
|
||||
Arg.Any<ICollection<EmergencyAccessDetails>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegenerateKeysAsync_Success(SutProvider<AccountsKeyManagementController> sutProvider,
|
||||
KeyRegenerationRequestModel data, User user, ICollection<OrganizationUser> orgUsers,
|
||||
ICollection<EmergencyAccessDetails> accessDetails)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(Arg.Is(FeatureFlagKeys.PrivateKeyRegeneration))
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyByUserAsync(Arg.Is(user.Id)).Returns(orgUsers);
|
||||
sutProvider.GetDependency<IEmergencyAccessRepository>().GetManyDetailsByGranteeIdAsync(Arg.Is(user.Id))
|
||||
.Returns(accessDetails);
|
||||
|
||||
await sutProvider.Sut.RegenerateKeysAsync(data);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
|
||||
.GetManyByUserAsync(Arg.Is(user.Id));
|
||||
await sutProvider.GetDependency<IEmergencyAccessRepository>().Received(1)
|
||||
.GetManyDetailsByGranteeIdAsync(Arg.Is(user.Id));
|
||||
await sutProvider.GetDependency<IRegenerateUserAsymmetricKeysCommand>().Received(1)
|
||||
.RegenerateKeysAsync(
|
||||
Arg.Is<UserAsymmetricKeys>(u =>
|
||||
u.UserId == user.Id && u.PublicKey == data.UserPublicKey &&
|
||||
u.UserKeyEncryptedPrivateKey == data.UserKeyEncryptedUserPrivateKey),
|
||||
Arg.Is(orgUsers),
|
||||
Arg.Is(accessDetails));
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
using Bit.Api.Vault.Models.Request;
|
||||
using Bit.Api.Vault.Validators;
|
||||
using Bit.Api.KeyManagement.Validators;
|
||||
using Bit.Api.Vault.Models.Request;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Vault.Models.Data;
|
||||
@ -9,7 +9,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.Vault.Validators;
|
||||
namespace Bit.Api.Test.KeyManagement.Validators;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class CipherRotationValidatorTests
|
@ -1,5 +1,5 @@
|
||||
using Bit.Api.Auth.Models.Request;
|
||||
using Bit.Api.Auth.Validators;
|
||||
using Bit.Api.KeyManagement.Validators;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
@ -10,7 +10,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.Auth.Validators;
|
||||
namespace Bit.Api.Test.KeyManagement.Validators;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class EmergencyAccessRotationValidatorTests
|
@ -1,5 +1,5 @@
|
||||
using Bit.Api.Vault.Models.Request;
|
||||
using Bit.Api.Vault.Validators;
|
||||
using Bit.Api.KeyManagement.Validators;
|
||||
using Bit.Api.Vault.Models.Request;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Vault.Entities;
|
||||
@ -9,7 +9,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.Vault.Validators;
|
||||
namespace Bit.Api.Test.KeyManagement.Validators;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class FolderRotationValidatorTests
|
@ -1,5 +1,5 @@
|
||||
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||
using Bit.Api.AdminConsole.Validators;
|
||||
using Bit.Api.KeyManagement.Validators;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
@ -8,7 +8,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.AdminConsole.Validators;
|
||||
namespace Bit.Api.Test.KeyManagement.Validators;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationUserRotationValidatorTests
|
@ -1,7 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Api.KeyManagement.Validators;
|
||||
using Bit.Api.Tools.Models;
|
||||
using Bit.Api.Tools.Models.Request;
|
||||
using Bit.Api.Tools.Validators;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Tools.Entities;
|
||||
@ -14,7 +14,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.Tools.Validators;
|
||||
namespace Bit.Api.Test.KeyManagement.Validators;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class SendRotationValidatorTests
|
@ -1,5 +1,5 @@
|
||||
using Bit.Api.Auth.Models.Request.WebAuthn;
|
||||
using Bit.Api.Auth.Validators;
|
||||
using Bit.Api.KeyManagement.Validators;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Entities;
|
||||
@ -9,7 +9,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.Auth.Validators;
|
||||
namespace Bit.Api.Test.KeyManagement.Validators;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class WebAuthnLoginKeyRotationValidatorTests
|
@ -0,0 +1,202 @@
|
||||
#nullable enable
|
||||
using Bit.Api.NotificationCenter.Controllers;
|
||||
using Bit.Api.NotificationCenter.Models.Request;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.NotificationCenter.Commands.Interfaces;
|
||||
using Bit.Core.NotificationCenter.Models.Data;
|
||||
using Bit.Core.NotificationCenter.Models.Filter;
|
||||
using Bit.Core.NotificationCenter.Queries.Interfaces;
|
||||
using Bit.Core.Test.NotificationCenter.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.NotificationCenter.Controllers;
|
||||
|
||||
[ControllerCustomize(typeof(NotificationsController))]
|
||||
[SutProviderCustomize]
|
||||
public class NotificationsControllerTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData([null, null])]
|
||||
[BitAutoData([null, false])]
|
||||
[BitAutoData([null, true])]
|
||||
[BitAutoData(false, null)]
|
||||
[BitAutoData(true, null)]
|
||||
[BitAutoData(false, false)]
|
||||
[BitAutoData(false, true)]
|
||||
[BitAutoData(true, false)]
|
||||
[BitAutoData(true, true)]
|
||||
[NotificationStatusDetailsListCustomize(5)]
|
||||
public async Task ListAsync_StatusFilter_ReturnedMatchingNotifications(bool? readStatusFilter, bool? deletedStatusFilter,
|
||||
SutProvider<NotificationsController> sutProvider,
|
||||
IEnumerable<NotificationStatusDetails> notificationStatusDetailsEnumerable)
|
||||
{
|
||||
var notificationStatusDetailsList = notificationStatusDetailsEnumerable
|
||||
.OrderByDescending(n => n.Priority)
|
||||
.ThenByDescending(n => n.CreationDate)
|
||||
.ToList();
|
||||
|
||||
sutProvider.GetDependency<IGetNotificationStatusDetailsForUserQuery>()
|
||||
.GetByUserIdStatusFilterAsync(Arg.Any<NotificationStatusFilter>(), Arg.Any<PageOptions>())
|
||||
.Returns(new PagedResult<NotificationStatusDetails> { Data = notificationStatusDetailsList });
|
||||
|
||||
var expectedNotificationStatusDetailsMap = notificationStatusDetailsList
|
||||
.Take(10)
|
||||
.ToDictionary(n => n.Id);
|
||||
|
||||
var listResponse = await sutProvider.Sut.ListAsync(new NotificationFilterRequestModel
|
||||
{
|
||||
ReadStatusFilter = readStatusFilter,
|
||||
DeletedStatusFilter = deletedStatusFilter
|
||||
});
|
||||
|
||||
Assert.Equal("list", listResponse.Object);
|
||||
Assert.Equal(5, listResponse.Data.Count());
|
||||
Assert.All(listResponse.Data, notificationResponseModel =>
|
||||
{
|
||||
Assert.Equal("notification", notificationResponseModel.Object);
|
||||
Assert.True(expectedNotificationStatusDetailsMap.ContainsKey(notificationResponseModel.Id));
|
||||
var expectedNotificationStatusDetails = expectedNotificationStatusDetailsMap[notificationResponseModel.Id];
|
||||
Assert.NotNull(expectedNotificationStatusDetails);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Id, notificationResponseModel.Id);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Priority, notificationResponseModel.Priority);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Title, notificationResponseModel.Title);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Body, notificationResponseModel.Body);
|
||||
Assert.Equal(expectedNotificationStatusDetails.RevisionDate, notificationResponseModel.Date);
|
||||
Assert.Equal(expectedNotificationStatusDetails.ReadDate, notificationResponseModel.ReadDate);
|
||||
Assert.Equal(expectedNotificationStatusDetails.DeletedDate, notificationResponseModel.DeletedDate);
|
||||
});
|
||||
Assert.Null(listResponse.ContinuationToken);
|
||||
|
||||
await sutProvider.GetDependency<IGetNotificationStatusDetailsForUserQuery>()
|
||||
.Received(1)
|
||||
.GetByUserIdStatusFilterAsync(Arg.Is<NotificationStatusFilter>(filter =>
|
||||
filter.Read == readStatusFilter && filter.Deleted == deletedStatusFilter),
|
||||
Arg.Is<PageOptions>(pageOptions =>
|
||||
pageOptions.ContinuationToken == null && pageOptions.PageSize == 10));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationStatusDetailsListCustomize(19)]
|
||||
public async Task ListAsync_PagingRequestNoContinuationToken_ReturnedFirst10MatchingNotifications(
|
||||
SutProvider<NotificationsController> sutProvider,
|
||||
IEnumerable<NotificationStatusDetails> notificationStatusDetailsEnumerable)
|
||||
{
|
||||
var notificationStatusDetailsList = notificationStatusDetailsEnumerable
|
||||
.OrderByDescending(n => n.Priority)
|
||||
.ThenByDescending(n => n.CreationDate)
|
||||
.ToList();
|
||||
|
||||
sutProvider.GetDependency<IGetNotificationStatusDetailsForUserQuery>()
|
||||
.GetByUserIdStatusFilterAsync(Arg.Any<NotificationStatusFilter>(), Arg.Any<PageOptions>())
|
||||
.Returns(new PagedResult<NotificationStatusDetails>
|
||||
{ Data = notificationStatusDetailsList.Take(10).ToList(), ContinuationToken = "2" });
|
||||
|
||||
var expectedNotificationStatusDetailsMap = notificationStatusDetailsList
|
||||
.Take(10)
|
||||
.ToDictionary(n => n.Id);
|
||||
|
||||
var listResponse = await sutProvider.Sut.ListAsync(new NotificationFilterRequestModel());
|
||||
|
||||
Assert.Equal("list", listResponse.Object);
|
||||
Assert.Equal(10, listResponse.Data.Count());
|
||||
Assert.All(listResponse.Data, notificationResponseModel =>
|
||||
{
|
||||
Assert.Equal("notification", notificationResponseModel.Object);
|
||||
Assert.True(expectedNotificationStatusDetailsMap.ContainsKey(notificationResponseModel.Id));
|
||||
var expectedNotificationStatusDetails = expectedNotificationStatusDetailsMap[notificationResponseModel.Id];
|
||||
Assert.NotNull(expectedNotificationStatusDetails);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Id, notificationResponseModel.Id);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Priority, notificationResponseModel.Priority);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Title, notificationResponseModel.Title);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Body, notificationResponseModel.Body);
|
||||
Assert.Equal(expectedNotificationStatusDetails.RevisionDate, notificationResponseModel.Date);
|
||||
Assert.Equal(expectedNotificationStatusDetails.ReadDate, notificationResponseModel.ReadDate);
|
||||
Assert.Equal(expectedNotificationStatusDetails.DeletedDate, notificationResponseModel.DeletedDate);
|
||||
});
|
||||
Assert.Equal("2", listResponse.ContinuationToken);
|
||||
|
||||
await sutProvider.GetDependency<IGetNotificationStatusDetailsForUserQuery>()
|
||||
.Received(1)
|
||||
.GetByUserIdStatusFilterAsync(Arg.Any<NotificationStatusFilter>(),
|
||||
Arg.Is<PageOptions>(pageOptions =>
|
||||
pageOptions.ContinuationToken == null && pageOptions.PageSize == 10));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationStatusDetailsListCustomize(19)]
|
||||
public async Task ListAsync_PagingRequestUsingContinuationToken_ReturnedLast9MatchingNotifications(
|
||||
SutProvider<NotificationsController> sutProvider,
|
||||
IEnumerable<NotificationStatusDetails> notificationStatusDetailsEnumerable)
|
||||
{
|
||||
var notificationStatusDetailsList = notificationStatusDetailsEnumerable
|
||||
.OrderByDescending(n => n.Priority)
|
||||
.ThenByDescending(n => n.CreationDate)
|
||||
.ToList();
|
||||
|
||||
sutProvider.GetDependency<IGetNotificationStatusDetailsForUserQuery>()
|
||||
.GetByUserIdStatusFilterAsync(Arg.Any<NotificationStatusFilter>(), Arg.Any<PageOptions>())
|
||||
.Returns(new PagedResult<NotificationStatusDetails>
|
||||
{ Data = notificationStatusDetailsList.Skip(10).ToList() });
|
||||
|
||||
var expectedNotificationStatusDetailsMap = notificationStatusDetailsList
|
||||
.Skip(10)
|
||||
.ToDictionary(n => n.Id);
|
||||
|
||||
var listResponse = await sutProvider.Sut.ListAsync(new NotificationFilterRequestModel { ContinuationToken = "2" });
|
||||
|
||||
Assert.Equal("list", listResponse.Object);
|
||||
Assert.Equal(9, listResponse.Data.Count());
|
||||
Assert.All(listResponse.Data, notificationResponseModel =>
|
||||
{
|
||||
Assert.Equal("notification", notificationResponseModel.Object);
|
||||
Assert.True(expectedNotificationStatusDetailsMap.ContainsKey(notificationResponseModel.Id));
|
||||
var expectedNotificationStatusDetails = expectedNotificationStatusDetailsMap[notificationResponseModel.Id];
|
||||
Assert.NotNull(expectedNotificationStatusDetails);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Id, notificationResponseModel.Id);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Priority, notificationResponseModel.Priority);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Title, notificationResponseModel.Title);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Body, notificationResponseModel.Body);
|
||||
Assert.Equal(expectedNotificationStatusDetails.RevisionDate, notificationResponseModel.Date);
|
||||
Assert.Equal(expectedNotificationStatusDetails.ReadDate, notificationResponseModel.ReadDate);
|
||||
Assert.Equal(expectedNotificationStatusDetails.DeletedDate, notificationResponseModel.DeletedDate);
|
||||
});
|
||||
Assert.Null(listResponse.ContinuationToken);
|
||||
|
||||
await sutProvider.GetDependency<IGetNotificationStatusDetailsForUserQuery>()
|
||||
.Received(1)
|
||||
.GetByUserIdStatusFilterAsync(Arg.Any<NotificationStatusFilter>(),
|
||||
Arg.Is<PageOptions>(pageOptions =>
|
||||
pageOptions.ContinuationToken == "2" && pageOptions.PageSize == 10));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task MarkAsDeletedAsync_NotificationId_MarkedAsDeleted(
|
||||
SutProvider<NotificationsController> sutProvider,
|
||||
Guid notificationId)
|
||||
{
|
||||
await sutProvider.Sut.MarkAsDeletedAsync(notificationId);
|
||||
|
||||
await sutProvider.GetDependency<IMarkNotificationDeletedCommand>()
|
||||
.Received(1)
|
||||
.MarkDeletedAsync(notificationId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task MarkAsReadAsync_NotificationId_MarkedAsRead(
|
||||
SutProvider<NotificationsController> sutProvider,
|
||||
Guid notificationId)
|
||||
{
|
||||
await sutProvider.Sut.MarkAsReadAsync(notificationId);
|
||||
|
||||
await sutProvider.GetDependency<IMarkNotificationReadCommand>()
|
||||
.Received(1)
|
||||
.MarkReadAsync(notificationId);
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
#nullable enable
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Api.NotificationCenter.Models.Request;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.NotificationCenter.Models.Request;
|
||||
|
||||
public class NotificationFilterRequestModelTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("invalid")]
|
||||
[InlineData("-1")]
|
||||
[InlineData("0")]
|
||||
public void Validate_ContinuationTokenInvalidNumber_Invalid(string continuationToken)
|
||||
{
|
||||
var model = new NotificationFilterRequestModel
|
||||
{
|
||||
ContinuationToken = continuationToken,
|
||||
};
|
||||
var result = Validate(model);
|
||||
Assert.Single(result);
|
||||
Assert.Contains("Continuation token must be a positive, non zero integer.", result[0].ErrorMessage);
|
||||
Assert.Contains("ContinuationToken", result[0].MemberNames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_ContinuationTokenMaxLengthExceeded_Invalid()
|
||||
{
|
||||
var model = new NotificationFilterRequestModel
|
||||
{
|
||||
ContinuationToken = "1234567890"
|
||||
};
|
||||
var result = Validate(model);
|
||||
Assert.Single(result);
|
||||
Assert.Contains("The field ContinuationToken must be a string with a maximum length of 9.",
|
||||
result[0].ErrorMessage);
|
||||
Assert.Contains("ContinuationToken", result[0].MemberNames);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("1")]
|
||||
[InlineData("123456789")]
|
||||
public void Validate_ContinuationTokenCorrect_Valid(string? continuationToken)
|
||||
{
|
||||
var model = new NotificationFilterRequestModel
|
||||
{
|
||||
ContinuationToken = continuationToken
|
||||
};
|
||||
var result = Validate(model);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(9)]
|
||||
[InlineData(1001)]
|
||||
public void Validate_PageSizeInvalidRange_Invalid(int pageSize)
|
||||
{
|
||||
var model = new NotificationFilterRequestModel
|
||||
{
|
||||
PageSize = pageSize
|
||||
};
|
||||
var result = Validate(model);
|
||||
Assert.Single(result);
|
||||
Assert.Contains("The field PageSize must be between 10 and 1000.", result[0].ErrorMessage);
|
||||
Assert.Contains("PageSize", result[0].MemberNames);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData(10)]
|
||||
[InlineData(1000)]
|
||||
public void Validate_PageSizeCorrect_Valid(int? pageSize)
|
||||
{
|
||||
var model = pageSize == null
|
||||
? new NotificationFilterRequestModel()
|
||||
: new NotificationFilterRequestModel
|
||||
{
|
||||
PageSize = pageSize.Value
|
||||
};
|
||||
var result = Validate(model);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
private static List<ValidationResult> Validate(NotificationFilterRequestModel model)
|
||||
{
|
||||
var results = new List<ValidationResult>();
|
||||
Validator.TryValidateObject(model, new ValidationContext(model), results, true);
|
||||
return results;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
#nullable enable
|
||||
using Bit.Api.NotificationCenter.Models.Response;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.NotificationCenter.Enums;
|
||||
using Bit.Core.NotificationCenter.Models.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.NotificationCenter.Models.Response;
|
||||
|
||||
public class NotificationResponseModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_NotificationStatusDetailsNull_CorrectFields()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => new NotificationResponseModel(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NotificationStatusDetails_CorrectFields()
|
||||
{
|
||||
var notificationStatusDetails = new NotificationStatusDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Global = true,
|
||||
Priority = Priority.High,
|
||||
ClientType = ClientType.All,
|
||||
Title = "Test Title",
|
||||
Body = "Test Body",
|
||||
RevisionDate = DateTime.UtcNow - TimeSpan.FromMinutes(3),
|
||||
ReadDate = DateTime.UtcNow - TimeSpan.FromMinutes(1),
|
||||
DeletedDate = DateTime.UtcNow,
|
||||
};
|
||||
var model = new NotificationResponseModel(notificationStatusDetails);
|
||||
|
||||
Assert.Equal(model.Id, notificationStatusDetails.Id);
|
||||
Assert.Equal(model.Priority, notificationStatusDetails.Priority);
|
||||
Assert.Equal(model.Title, notificationStatusDetails.Title);
|
||||
Assert.Equal(model.Body, notificationStatusDetails.Body);
|
||||
Assert.Equal(model.Date, notificationStatusDetails.RevisionDate);
|
||||
Assert.Equal(model.ReadDate, notificationStatusDetails.ReadDate);
|
||||
Assert.Equal(model.DeletedDate, notificationStatusDetails.DeletedDate);
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Api.Tools.Authorization;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Test.AdminConsole.Helpers;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.Tools.Authorization;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class VaultExportAuthorizationHandlerTests
|
||||
{
|
||||
public static IEnumerable<object[]> CanExportWholeVault => new List<CurrentContextOrganization>
|
||||
{
|
||||
new () { Type = OrganizationUserType.Owner },
|
||||
new () { Type = OrganizationUserType.Admin },
|
||||
new ()
|
||||
{
|
||||
Type = OrganizationUserType.Custom, Permissions = new Permissions { AccessImportExport = true }
|
||||
}
|
||||
}.Select(org => new[] { org });
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(CanExportWholeVault))]
|
||||
public async Task ExportAll_PermittedRoles_Success(CurrentContextOrganization org, OrganizationScope orgScope, ClaimsPrincipal user,
|
||||
SutProvider<VaultExportAuthorizationHandler> sutProvider)
|
||||
{
|
||||
org.Id = orgScope;
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(orgScope).Returns(org);
|
||||
|
||||
var authContext = new AuthorizationHandlerContext(new[] { VaultExportOperations.ExportWholeVault }, user, orgScope);
|
||||
await sutProvider.Sut.HandleAsync(authContext);
|
||||
|
||||
Assert.True(authContext.HasSucceeded);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> CannotExportWholeVault => new List<CurrentContextOrganization>
|
||||
{
|
||||
new () { Type = OrganizationUserType.User },
|
||||
new ()
|
||||
{
|
||||
Type = OrganizationUserType.Custom, Permissions = new Permissions { AccessImportExport = true }.Invert()
|
||||
}
|
||||
}.Select(org => new[] { org });
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(CannotExportWholeVault))]
|
||||
public async Task ExportAll_NotPermitted_Failure(CurrentContextOrganization org, OrganizationScope orgScope, ClaimsPrincipal user,
|
||||
SutProvider<VaultExportAuthorizationHandler> sutProvider)
|
||||
{
|
||||
org.Id = orgScope;
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(orgScope).Returns(org);
|
||||
|
||||
var authContext = new AuthorizationHandlerContext(new[] { VaultExportOperations.ExportWholeVault }, user, orgScope);
|
||||
await sutProvider.Sut.HandleAsync(authContext);
|
||||
|
||||
Assert.False(authContext.HasSucceeded);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> CanExportManagedCollections =>
|
||||
AuthorizationHelpers.AllRoles().Select(o => new[] { o });
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(CanExportManagedCollections))]
|
||||
public async Task ExportManagedCollections_PermittedRoles_Success(CurrentContextOrganization org, OrganizationScope orgScope, ClaimsPrincipal user,
|
||||
SutProvider<VaultExportAuthorizationHandler> sutProvider)
|
||||
{
|
||||
org.Id = orgScope;
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(orgScope).Returns(org);
|
||||
|
||||
var authContext = new AuthorizationHandlerContext(new[] { VaultExportOperations.ExportManagedCollections }, user, orgScope);
|
||||
await sutProvider.Sut.HandleAsync(authContext);
|
||||
|
||||
Assert.True(authContext.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData([null])]
|
||||
public async Task ExportManagedCollections_NotPermitted_Failure(CurrentContextOrganization org, OrganizationScope orgScope, ClaimsPrincipal user,
|
||||
SutProvider<VaultExportAuthorizationHandler> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(orgScope).Returns(org);
|
||||
|
||||
var authContext = new AuthorizationHandlerContext(new[] { VaultExportOperations.ExportManagedCollections }, user, orgScope);
|
||||
await sutProvider.Sut.HandleAsync(authContext);
|
||||
|
||||
Assert.False(authContext.HasSucceeded);
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
using Bit.Api.Tools.Controllers;
|
||||
using AutoFixture;
|
||||
using Bit.Api.Tools.Controllers;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Tools.ReportFeatures.Interfaces;
|
||||
using Bit.Core.Tools.ReportFeatures.Requests;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@ -45,5 +47,98 @@ public class ReportsControllerTests
|
||||
.Received(0);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_withAccess_success(SutProvider<ReportsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessReports(Arg.Any<Guid>()).Returns(true);
|
||||
|
||||
// Act
|
||||
var request = new Api.Tools.Models.PasswordHealthReportApplicationModel
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
Url = "https://example.com",
|
||||
};
|
||||
await sutProvider.Sut.AddPasswordHealthReportApplication(request);
|
||||
|
||||
// Assert
|
||||
_ = sutProvider.GetDependency<IAddPasswordHealthReportApplicationCommand>()
|
||||
.Received(1)
|
||||
.AddPasswordHealthReportApplicationAsync(Arg.Is<AddPasswordHealthReportApplicationRequest>(_ =>
|
||||
_.OrganizationId == request.OrganizationId && _.Url == request.Url));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_multiple_withAccess_success(
|
||||
SutProvider<ReportsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessReports(Arg.Any<Guid>()).Returns(true);
|
||||
|
||||
// Act
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.CreateMany<Api.Tools.Models.PasswordHealthReportApplicationModel>(2);
|
||||
await sutProvider.Sut.AddPasswordHealthReportApplications(request);
|
||||
|
||||
// Assert
|
||||
_ = sutProvider.GetDependency<IAddPasswordHealthReportApplicationCommand>()
|
||||
.Received(1)
|
||||
.AddPasswordHealthReportApplicationAsync(Arg.Any<IEnumerable<AddPasswordHealthReportApplicationRequest>>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task AddPasswordHealthReportApplicationAsync_withoutAccess(SutProvider<ReportsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessReports(Arg.Any<Guid>()).Returns(false);
|
||||
|
||||
// Act
|
||||
var request = new Api.Tools.Models.PasswordHealthReportApplicationModel
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
Url = "https://example.com",
|
||||
};
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () =>
|
||||
await sutProvider.Sut.AddPasswordHealthReportApplication(request));
|
||||
|
||||
// Assert
|
||||
_ = sutProvider.GetDependency<IAddPasswordHealthReportApplicationCommand>()
|
||||
.Received(0);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DropPasswordHealthReportApplicationAsync_withoutAccess(SutProvider<ReportsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessReports(Arg.Any<Guid>()).Returns(false);
|
||||
|
||||
// Act
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.Create<Api.Tools.Models.PasswordHealthReportApplicationModel>();
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () =>
|
||||
await sutProvider.Sut.AddPasswordHealthReportApplication(request));
|
||||
|
||||
// Assert
|
||||
_ = sutProvider.GetDependency<IDropPasswordHealthReportApplicationCommand>()
|
||||
.Received(0);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DropPasswordHealthReportApplicationAsync_withAccess_success(SutProvider<ReportsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessReports(Arg.Any<Guid>()).Returns(true);
|
||||
|
||||
// Act
|
||||
var fixture = new Fixture();
|
||||
var request = fixture.Create<DropPasswordHealthReportApplicationRequest>();
|
||||
await sutProvider.Sut.DropPasswordHealthReportApplication(request);
|
||||
|
||||
// Assert
|
||||
_ = sutProvider.GetDependency<IDropPasswordHealthReportApplicationCommand>()
|
||||
.Received(1)
|
||||
.DropPasswordHealthReportApplicationAsync(Arg.Is<DropPasswordHealthReportApplicationRequest>(_ =>
|
||||
_.OrganizationId == request.OrganizationId &&
|
||||
_.PasswordHealthReportApplicationIds == request.PasswordHealthReportApplicationIds));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Api.Vault.AuthorizationHandlers.Collections;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@ -33,10 +32,7 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
organization.Type = userType;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
// `LimitCollectonCreationDeletionSplit` feature flag state isn't
|
||||
// relevant for this test. The flag is never checked for in this
|
||||
// test. This is asserted below.
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(sutProvider, organization, true, true);
|
||||
ArrangeOrganizationAbility(sutProvider, organization, true, true);
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Create },
|
||||
@ -48,12 +44,11 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().DidNotReceiveWithAnyArgs().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task CanCreateAsync_WhenUser_WithLimitCollectionCreationFalse_WithLimitCollectionCreationDeletionSplitFeatureDisabled_Success(
|
||||
public async Task CanCreateAsync_WhenUser_WithLimitCollectionCreationFalse_Success(
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<Collection> collections,
|
||||
CurrentContextOrganization organization)
|
||||
@ -62,7 +57,7 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
|
||||
organization.Type = OrganizationUserType.User;
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureDisabled(sutProvider, organization, false, false);
|
||||
ArrangeOrganizationAbility(sutProvider, organization, false, false);
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Create },
|
||||
@ -71,49 +66,16 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit)
|
||||
.Returns(false);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task CanCreateAsync_WhenUser_WithLimitCollectionCreationFalse_WithLimitCollectionCreationDeletionSplitFeatureEnabled_Success(
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<Collection> collections,
|
||||
CurrentContextOrganization organization)
|
||||
{
|
||||
var actingUserId = Guid.NewGuid();
|
||||
|
||||
organization.Type = OrganizationUserType.User;
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(sutProvider, organization, false, false);
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Create },
|
||||
new ClaimsPrincipal(),
|
||||
collections);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit)
|
||||
.Returns(true);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, CollectionCustomization]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
[BitAutoData(OrganizationUserType.Custom)]
|
||||
public async Task CanCreateAsync_WhenMissingPermissions_WithLimitCollectionCreationDeletionSplitFeatureDisabled_NoSuccess(
|
||||
public async Task CanCreateAsync_WhenMissingPermissions_NoSuccess(
|
||||
OrganizationUserType userType,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<Collection> collections,
|
||||
@ -130,7 +92,7 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
ManageUsers = false
|
||||
};
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureDisabled(sutProvider, organization, true, true);
|
||||
ArrangeOrganizationAbility(sutProvider, organization, true, true);
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Create },
|
||||
@ -140,61 +102,21 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(false);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, CollectionCustomization]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
[BitAutoData(OrganizationUserType.Custom)]
|
||||
public async Task CanCreateAsync_WhenMissingPermissions_WithLimitCollectionCreationDeletionSplitFeatureEnabled_NoSuccess(
|
||||
OrganizationUserType userType,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<Collection> collections,
|
||||
CurrentContextOrganization organization)
|
||||
{
|
||||
var actingUserId = Guid.NewGuid();
|
||||
|
||||
organization.Type = userType;
|
||||
organization.Permissions = new Permissions
|
||||
{
|
||||
EditAnyCollection = false,
|
||||
DeleteAnyCollection = false,
|
||||
ManageGroups = false,
|
||||
ManageUsers = false
|
||||
};
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(sutProvider, organization, true, true);
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Create },
|
||||
new ClaimsPrincipal(),
|
||||
collections);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(true);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task CanCreateAsync_WhenMissingOrgAccess_WithLimitCollectionCreationDeletionSplitDisabled_NoSuccess(
|
||||
public async Task CanCreateAsync_WhenMissingOrgAccess_NoSuccess(
|
||||
Guid userId,
|
||||
CurrentContextOrganization organization,
|
||||
List<Collection> collections,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider)
|
||||
{
|
||||
collections.ForEach(c => c.OrganizationId = organization.Id);
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureDisabled(sutProvider, organization, true, true);
|
||||
ArrangeOrganizationAbility(sutProvider, organization, true, true);
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Create },
|
||||
@ -205,38 +127,9 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(Arg.Any<Guid>()).Returns((CurrentContextOrganization)null);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(false);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task CanCreateAsync_WhenMissingOrgAccess_WithLimitCollectionCreationDeletionSplitEnabled_NoSuccess(
|
||||
Guid userId,
|
||||
CurrentContextOrganization organization,
|
||||
List<Collection> collections,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider)
|
||||
{
|
||||
collections.ForEach(c => c.OrganizationId = organization.Id);
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(sutProvider, organization, true, true);
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Create },
|
||||
new ClaimsPrincipal(),
|
||||
collections
|
||||
);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(Arg.Any<Guid>()).Returns((CurrentContextOrganization)null);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(true);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
@ -1015,7 +908,7 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
// `LimitCollectonCreationDeletionSplit` feature flag state isn't
|
||||
// relevant for this test. The flag is never checked for in this
|
||||
// test. This is asserted below.
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(sutProvider, organization, true, true);
|
||||
ArrangeOrganizationAbility(sutProvider, organization, true, true);
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Delete },
|
||||
@ -1027,7 +920,6 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().DidNotReceiveWithAnyArgs().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
@ -1046,7 +938,7 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
// `LimitCollectonCreationDeletionSplit` feature flag state isn't
|
||||
// relevant for this test. The flag is never checked for in this
|
||||
// test. This is asserted below.
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(sutProvider, organization, true, true);
|
||||
ArrangeOrganizationAbility(sutProvider, organization, true, true);
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Delete },
|
||||
@ -1058,12 +950,11 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().DidNotReceiveWithAnyArgs().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task CanDeleteAsync_WhenUser_LimitCollectionDeletionFalse_WithCanManagePermission_WithLimitCollectionCreationDeletionSplitFeatureDisabled_Success(
|
||||
public async Task CanDeleteAsync_WhenUser_LimitCollectionDeletionFalse_WithCanManagePermission_Success(
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<CollectionDetails> collections,
|
||||
CurrentContextOrganization organization)
|
||||
@ -1073,12 +964,11 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
organization.Type = OrganizationUserType.User;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureDisabled(sutProvider, organization, false, false);
|
||||
ArrangeOrganizationAbility(sutProvider, organization, false, false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(false);
|
||||
|
||||
foreach (var c in collections)
|
||||
{
|
||||
@ -1092,41 +982,6 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task CanDeleteAsync_WhenUser_LimitCollectionDeletionFalse_WithCanManagePermission_WithLimitCollectionCreationDeletionSplitFeatureEnabled_Success(
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<CollectionDetails> collections,
|
||||
CurrentContextOrganization organization)
|
||||
{
|
||||
var actingUserId = Guid.NewGuid();
|
||||
|
||||
organization.Type = OrganizationUserType.User;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(sutProvider, organization, false, false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(true);
|
||||
|
||||
foreach (var c in collections)
|
||||
{
|
||||
c.Manage = true;
|
||||
}
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Delete },
|
||||
new ClaimsPrincipal(),
|
||||
collections);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
@ -1134,7 +989,7 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
public async Task CanDeleteAsync_LimitCollectionDeletionFalse_AllowAdminAccessToAllCollectionItemsFalse_WithCanManagePermission_WithLimitCollectionCreationDeletionSplitFeatureDisabled_Success(
|
||||
public async Task CanDeleteAsync_LimitCollectionDeletionFalse_AllowAdminAccessToAllCollectionItemsFalse_WithCanManagePermission_Success(
|
||||
OrganizationUserType userType,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<CollectionDetails> collections,
|
||||
@ -1145,12 +1000,11 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
organization.Type = userType;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureDisabled(sutProvider, organization, false, false, false);
|
||||
ArrangeOrganizationAbility(sutProvider, organization, false, false, false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(false);
|
||||
|
||||
foreach (var c in collections)
|
||||
{
|
||||
@ -1164,15 +1018,13 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, CollectionCustomization]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
public async Task CanDeleteAsync_LimitCollectionDeletionFalse_AllowAdminAccessToAllCollectionItemsFalse_WithCanManagePermission_WithLimitCollectionCreationDeletionSplitFeatureEnabled_Success(
|
||||
public async Task CanDeleteAsync_WhenAdminOrOwner_LimitCollectionDeletionTrue_AllowAdminAccessToAllCollectionItemsFalse_WithCanManagePermission_Success(
|
||||
OrganizationUserType userType,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<CollectionDetails> collections,
|
||||
@ -1183,12 +1035,11 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
organization.Type = userType;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(sutProvider, organization, false, false, false);
|
||||
ArrangeOrganizationAbility(sutProvider, organization, true, true, false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(true);
|
||||
|
||||
foreach (var c in collections)
|
||||
{
|
||||
@ -1202,14 +1053,13 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, CollectionCustomization]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
public async Task CanDeleteAsync_WhenAdminOrOwner_LimitCollectionDeletionTrue_AllowAdminAccessToAllCollectionItemsFalse_WithCanManagePermission_WithLimitCollectionCreationDeletionSplitFeatureDisabled_Success(
|
||||
public async Task CanDeleteAsync_WhenAdminOrOwner_LimitCollectionDeletionTrue_AllowAdminAccessToAllCollectionItemsFalse_WithoutCanManagePermission_Failure(
|
||||
OrganizationUserType userType,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<CollectionDetails> collections,
|
||||
@ -1220,87 +1070,12 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
organization.Type = userType;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureDisabled(sutProvider, organization, true, true, false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(false);
|
||||
|
||||
foreach (var c in collections)
|
||||
{
|
||||
c.Manage = true;
|
||||
}
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Delete },
|
||||
new ClaimsPrincipal(),
|
||||
collections);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, CollectionCustomization]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
public async Task CanDeleteAsync_WhenAdminOrOwner_LimitCollectionDeletionTrue_AllowAdminAccessToAllCollectionItemsFalse_WithCanManagePermission_WithLimitCollectionCreationDeletionSplitFeatureEnabled_Success(
|
||||
OrganizationUserType userType,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<CollectionDetails> collections,
|
||||
CurrentContextOrganization organization)
|
||||
{
|
||||
var actingUserId = Guid.NewGuid();
|
||||
|
||||
organization.Type = userType;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(sutProvider, organization, true, true, false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(true);
|
||||
|
||||
foreach (var c in collections)
|
||||
{
|
||||
c.Manage = true;
|
||||
}
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Delete },
|
||||
new ClaimsPrincipal(),
|
||||
collections);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, CollectionCustomization]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
public async Task CanDeleteAsync_WhenAdminOrOwner_LimitCollectionDeletionTrue_AllowAdminAccessToAllCollectionItemsFalse_WithoutCanManagePermission_WithLimitCollectionCreationDeletionSplitFeatureDisabled_Failure(
|
||||
OrganizationUserType userType,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<CollectionDetails> collections,
|
||||
CurrentContextOrganization organization)
|
||||
{
|
||||
var actingUserId = Guid.NewGuid();
|
||||
|
||||
organization.Type = userType;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureDisabled(sutProvider, organization, true, true, false);
|
||||
ArrangeOrganizationAbility(sutProvider, organization, true, true, false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(false);
|
||||
|
||||
foreach (var c in collections)
|
||||
{
|
||||
@ -1314,50 +1089,11 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, CollectionCustomization]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
public async Task CanDeleteAsync_WhenAdminOrOwner_LimitCollectionDeletionTrue_AllowAdminAccessToAllCollectionItemsFalse_WithoutCanManagePermission_WithLimitCollectionCreationDeletionSplitFeatureEnabled_Failure(
|
||||
OrganizationUserType userType,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<CollectionDetails> collections,
|
||||
CurrentContextOrganization organization)
|
||||
{
|
||||
var actingUserId = Guid.NewGuid();
|
||||
|
||||
organization.Type = userType;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(sutProvider, organization, true, true, false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(true);
|
||||
|
||||
foreach (var c in collections)
|
||||
{
|
||||
c.Manage = false;
|
||||
}
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Delete },
|
||||
new ClaimsPrincipal(),
|
||||
collections);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task CanDeleteAsync_WhenUser_LimitCollectionDeletionTrue_AllowAdminAccessToAllCollectionItemsTrue_WithLimitCollectionCreationDeletionSplitFeatureDisabled_Failure(
|
||||
public async Task CanDeleteAsync_WhenUser_LimitCollectionDeletionTrue_AllowAdminAccessToAllCollectionItemsTrue_Failure(
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<CollectionDetails> collections,
|
||||
CurrentContextOrganization organization)
|
||||
@ -1367,13 +1103,12 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
organization.Type = OrganizationUserType.User;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureDisabled(sutProvider, organization, true, true);
|
||||
ArrangeOrganizationAbility(sutProvider, organization, true, true);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(false);
|
||||
|
||||
foreach (var c in collections)
|
||||
{
|
||||
@ -1387,12 +1122,11 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task CanDeleteAsync_WhenUser_LimitCollectionDeletionTrue_AllowAdminAccessToAllCollectionItemsTrue_WithLimitCollectionCreationDeletionSplitFeatureEnabled_Failure(
|
||||
public async Task CanDeleteAsync_WhenUser_LimitCollectionDeletionTrue_AllowAdminAccessToAllCollectionItemsFalse_Failure(
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<CollectionDetails> collections,
|
||||
CurrentContextOrganization organization)
|
||||
@ -1402,13 +1136,12 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
organization.Type = OrganizationUserType.User;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(sutProvider, organization, true, true);
|
||||
ArrangeOrganizationAbility(sutProvider, organization, true, true, false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(true);
|
||||
|
||||
foreach (var c in collections)
|
||||
{
|
||||
@ -1422,88 +1155,13 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task CanDeleteAsync_WhenUser_LimitCollectionDeletionTrue_AllowAdminAccessToAllCollectionItemsFalse_WithLimitCollectionCreationDeletionSplitFeatureDisabled_Failure(
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<CollectionDetails> collections,
|
||||
CurrentContextOrganization organization)
|
||||
{
|
||||
var actingUserId = Guid.NewGuid();
|
||||
|
||||
organization.Type = OrganizationUserType.User;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureDisabled(sutProvider, organization, true, true, false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit)
|
||||
.Returns(false);
|
||||
|
||||
foreach (var c in collections)
|
||||
{
|
||||
c.Manage = true;
|
||||
}
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Delete },
|
||||
new ClaimsPrincipal(),
|
||||
collections);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task CanDeleteAsync_WhenUser_LimitCollectionDeletionTrue_AllowAdminAccessToAllCollectionItemsFalse_WithLimitCollectionCreationDeletionSplitFeatureEnabled_Failure(
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<CollectionDetails> collections,
|
||||
CurrentContextOrganization organization)
|
||||
{
|
||||
var actingUserId = Guid.NewGuid();
|
||||
|
||||
organization.Type = OrganizationUserType.User;
|
||||
organization.Permissions = new Permissions();
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(sutProvider, organization, true, true, false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICollectionRepository>().GetManyByUserIdAsync(actingUserId).Returns(collections);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit)
|
||||
.Returns(true);
|
||||
|
||||
foreach (var c in collections)
|
||||
{
|
||||
c.Manage = true;
|
||||
}
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Delete },
|
||||
new ClaimsPrincipal(),
|
||||
collections);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, CollectionCustomization]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
[BitAutoData(OrganizationUserType.Custom)]
|
||||
public async Task CanDeleteAsync_WhenMissingPermissions_WithLimitCollectionCreationDeletionSplitFeatureDisabled_NoSuccess(
|
||||
public async Task CanDeleteAsync_WhenMissingPermissions_NoSuccess(
|
||||
OrganizationUserType userType,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<Collection> collections,
|
||||
@ -1520,7 +1178,7 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
ManageUsers = false
|
||||
};
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureDisabled(sutProvider, organization, true, true);
|
||||
ArrangeOrganizationAbility(sutProvider, organization, true, true);
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Delete },
|
||||
@ -1530,54 +1188,14 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(false);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, CollectionCustomization]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
[BitAutoData(OrganizationUserType.Custom)]
|
||||
public async Task CanDeleteAsync_WhenMissingPermissions_WithLimitCollectionCreationDeletionSplitFeatureEnabled_NoSuccess(
|
||||
OrganizationUserType userType,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<Collection> collections,
|
||||
CurrentContextOrganization organization)
|
||||
{
|
||||
var actingUserId = Guid.NewGuid();
|
||||
|
||||
organization.Type = userType;
|
||||
organization.Permissions = new Permissions
|
||||
{
|
||||
EditAnyCollection = false,
|
||||
DeleteAnyCollection = false,
|
||||
ManageGroups = false,
|
||||
ManageUsers = false
|
||||
};
|
||||
|
||||
ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(sutProvider, organization, true, true);
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Delete },
|
||||
new ClaimsPrincipal(),
|
||||
collections);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(true);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task CanDeleteAsync_WhenMissingOrgAccess_WithLimitCollectionCreationDeletionSplitFeatureDisabled_NoSuccess(
|
||||
public async Task CanDeleteAsync_WhenMissingOrgAccess_NoSuccess(
|
||||
Guid userId,
|
||||
ICollection<Collection> collections,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider)
|
||||
@ -1591,34 +1209,9 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(Arg.Any<Guid>()).Returns((CurrentContextOrganization)null);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(false);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task CanDeleteAsync_WhenMissingOrgAccess_WithLimitCollectionCreationDeletionSplitFeatureEnabled_NoSuccess(
|
||||
Guid userId,
|
||||
ICollection<Collection> collections,
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider)
|
||||
{
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { BulkCollectionOperations.Delete },
|
||||
new ClaimsPrincipal(),
|
||||
collections
|
||||
);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(Arg.Any<Guid>()).Returns((CurrentContextOrganization)null);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(false);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(true);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().Received(1).IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
@ -1639,7 +1232,6 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
Assert.True(context.HasFailed);
|
||||
sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs();
|
||||
sutProvider.GetDependency<IFeatureService>().DidNotReceiveWithAnyArgs().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
@ -1663,66 +1255,10 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.HandleAsync(context));
|
||||
Assert.Equal("Requested collections must belong to the same organization.", exception.Message);
|
||||
sutProvider.GetDependency<ICurrentContext>().DidNotReceiveWithAnyArgs().GetOrganization(default);
|
||||
sutProvider.GetDependency<IFeatureService>().DidNotReceiveWithAnyArgs().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task HandleRequirementAsync_Provider_WithLimitCollectionCreationDeletionSplitFeatureDisabled_Success(
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<Collection> collections)
|
||||
{
|
||||
var actingUserId = Guid.NewGuid();
|
||||
var orgId = collections.First().OrganizationId;
|
||||
|
||||
var organizationAbilities = new Dictionary<Guid, OrganizationAbility>
|
||||
{
|
||||
{ collections.First().OrganizationId,
|
||||
new OrganizationAbility
|
||||
{
|
||||
LimitCollectionCreationDeletion = true,
|
||||
AllowAdminAccessToAllCollectionItems = true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var operationsToTest = new[]
|
||||
{
|
||||
BulkCollectionOperations.Create,
|
||||
BulkCollectionOperations.Read,
|
||||
BulkCollectionOperations.ReadAccess,
|
||||
BulkCollectionOperations.Update,
|
||||
BulkCollectionOperations.ModifyUserAccess,
|
||||
BulkCollectionOperations.ModifyGroupAccess,
|
||||
BulkCollectionOperations.Delete,
|
||||
};
|
||||
|
||||
foreach (var op in operationsToTest)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(actingUserId);
|
||||
sutProvider.GetDependency<ICurrentContext>().GetOrganization(orgId).Returns((CurrentContextOrganization)null);
|
||||
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilitiesAsync()
|
||||
.Returns(organizationAbilities);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(true);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(false);
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { op },
|
||||
new ClaimsPrincipal(),
|
||||
collections
|
||||
);
|
||||
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
Assert.True(context.HasSucceeded);
|
||||
await sutProvider.GetDependency<ICurrentContext>().Received().ProviderUserForOrgAsync(orgId);
|
||||
|
||||
// Recreate the SUT to reset the mocks/dependencies between tests
|
||||
sutProvider.Recreate();
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, BitAutoData, CollectionCustomization]
|
||||
public async Task HandleRequirementAsync_Provider_WithLimitCollectionCreationDeletionSplitFeatureEnabled_Success(
|
||||
public async Task HandleRequirementAsync_Provider_Success(
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
ICollection<Collection> collections)
|
||||
{
|
||||
@ -1759,7 +1295,6 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilitiesAsync()
|
||||
.Returns(organizationAbilities);
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderUserForOrgAsync(Arg.Any<Guid>()).Returns(true);
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit).Returns(true);
|
||||
|
||||
var context = new AuthorizationHandlerContext(
|
||||
new[] { op },
|
||||
@ -1810,30 +1345,12 @@ public class BulkCollectionAuthorizationHandlerTests
|
||||
await sutProvider.GetDependency<ICollectionRepository>().Received(1).GetManyByUserIdAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
private static void ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureDisabled(
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
CurrentContextOrganization organization,
|
||||
bool limitCollectionCreation,
|
||||
bool limitCollectionDeletion,
|
||||
bool allowAdminAccessToAllCollectionItems = true)
|
||||
{
|
||||
var organizationAbility = new OrganizationAbility();
|
||||
organizationAbility.Id = organization.Id;
|
||||
|
||||
organizationAbility.LimitCollectionCreationDeletion = limitCollectionCreation || limitCollectionDeletion;
|
||||
|
||||
organizationAbility.AllowAdminAccessToAllCollectionItems = allowAdminAccessToAllCollectionItems;
|
||||
|
||||
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilityAsync(organizationAbility.Id)
|
||||
.Returns(organizationAbility);
|
||||
}
|
||||
|
||||
private static void ArrangeOrganizationAbility_WithLimitCollectionCreationDeletionSplitFeatureEnabled(
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
CurrentContextOrganization organization,
|
||||
bool limitCollectionCreation,
|
||||
bool limitCollectionDeletion,
|
||||
bool allowAdminAccessToAllCollectionItems = true)
|
||||
private static void ArrangeOrganizationAbility(
|
||||
SutProvider<BulkCollectionAuthorizationHandler> sutProvider,
|
||||
CurrentContextOrganization organization,
|
||||
bool limitCollectionCreation,
|
||||
bool limitCollectionDeletion,
|
||||
bool allowAdminAccessToAllCollectionItems = true)
|
||||
{
|
||||
var organizationAbility = new OrganizationAbility();
|
||||
organizationAbility.Id = organization.Id;
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Divergic.Logging.Xunit" Version="4.3.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" />
|
||||
<PackageReference Include="RichardSzalay.MockHttp" Version="7.0.0" />
|
||||
<PackageReference Include="xunit" Version="$(XUnitVersion)" />
|
||||
|
@ -2,6 +2,7 @@
|
||||
using AutoFixture;
|
||||
using AutoFixture.Xunit2;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
@ -12,7 +13,8 @@ internal class PolicyUpdateCustomization(PolicyType type, bool enabled) : ICusto
|
||||
{
|
||||
fixture.Customize<PolicyUpdate>(composer => composer
|
||||
.With(o => o.Type, type)
|
||||
.With(o => o.Enabled, enabled));
|
||||
.With(o => o.Enabled, enabled)
|
||||
.With(o => o.PerformedBy, new StandardUser(Guid.NewGuid(), false)));
|
||||
}
|
||||
}
|
||||
|
||||
|
52
test/Core.Test/AdminConsole/Helpers/AuthorizationHelpers.cs
Normal file
52
test/Core.Test/AdminConsole/Helpers/AuthorizationHelpers.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.Helpers;
|
||||
|
||||
public static class AuthorizationHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Return a new Permission object with inverted permissions.
|
||||
/// This is useful to test negative cases, e.g. "all other permissions should fail".
|
||||
/// </summary>
|
||||
/// <param name="permissions"></param>
|
||||
/// <returns></returns>
|
||||
public static Permissions Invert(this Permissions permissions)
|
||||
{
|
||||
// Get all false boolean properties of input object
|
||||
var inputsToFlip = permissions
|
||||
.GetType()
|
||||
.GetProperties()
|
||||
.Where(p =>
|
||||
p.PropertyType == typeof(bool) &&
|
||||
(bool)p.GetValue(permissions, null)! == false)
|
||||
.Select(p => p.Name);
|
||||
|
||||
var result = new Permissions();
|
||||
|
||||
// Set these to true on the result object
|
||||
result
|
||||
.GetType()
|
||||
.GetProperties()
|
||||
.Where(p => inputsToFlip.Contains(p.Name))
|
||||
.ToList()
|
||||
.ForEach(p => p.SetValue(result, true));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a sequence of all possible roles and permissions represented as CurrentContextOrganization objects.
|
||||
/// Used largely for authorization testing.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<CurrentContextOrganization> AllRoles() => new List<CurrentContextOrganization>
|
||||
{
|
||||
new () { Type = OrganizationUserType.Owner },
|
||||
new () { Type = OrganizationUserType.Admin },
|
||||
new () { Type = OrganizationUserType.Custom, Permissions = new Permissions() },
|
||||
new () { Type = OrganizationUserType.Custom, Permissions = new Permissions().Invert() },
|
||||
new () { Type = OrganizationUserType.User },
|
||||
};
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
using Bit.Core.Models.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.Helpers;
|
||||
|
||||
public class AuthorizationHelpersTests
|
||||
{
|
||||
[Fact]
|
||||
public void Permissions_Invert_InvertsAllPermissions()
|
||||
{
|
||||
var sut = new Permissions
|
||||
{
|
||||
AccessEventLogs = true,
|
||||
AccessReports = true,
|
||||
DeleteAnyCollection = true,
|
||||
ManagePolicies = true,
|
||||
ManageScim = true
|
||||
};
|
||||
|
||||
var result = sut.Invert();
|
||||
|
||||
Assert.True(result is
|
||||
{
|
||||
AccessEventLogs: false,
|
||||
AccessImportExport: true,
|
||||
AccessReports: false,
|
||||
CreateNewCollections: true,
|
||||
EditAnyCollection: true,
|
||||
DeleteAnyCollection: false,
|
||||
ManageGroups: true,
|
||||
ManagePolicies: false,
|
||||
ManageSso: true,
|
||||
ManageUsers: true,
|
||||
ManageResetPassword: true,
|
||||
ManageScim: false
|
||||
});
|
||||
}
|
||||
}
|
@ -1,10 +1,15 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
@ -28,7 +33,12 @@ public class VerifyOrganizationDomainCommandTests
|
||||
DomainName = "Test Domain",
|
||||
Txt = "btw+test18383838383"
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
expected.SetVerifiedDate();
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetByIdAsync(id)
|
||||
.Returns(expected);
|
||||
@ -53,6 +63,10 @@ public class VerifyOrganizationDomainCommandTests
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetByIdAsync(id)
|
||||
.Returns(expected);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetClaimedDomainsByDomainNameAsync(expected.DomainName)
|
||||
.Returns(new List<OrganizationDomain> { expected });
|
||||
@ -77,9 +91,14 @@ public class VerifyOrganizationDomainCommandTests
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetByIdAsync(id)
|
||||
.Returns(expected);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetClaimedDomainsByDomainNameAsync(expected.DomainName)
|
||||
.Returns(new List<OrganizationDomain>());
|
||||
|
||||
sutProvider.GetDependency<IDnsResolverService>()
|
||||
.ResolveAsync(expected.DomainName, Arg.Any<string>())
|
||||
.Returns(true);
|
||||
@ -107,9 +126,14 @@ public class VerifyOrganizationDomainCommandTests
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetByIdAsync(id)
|
||||
.Returns(expected);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetClaimedDomainsByDomainNameAsync(expected.DomainName)
|
||||
.Returns(new List<OrganizationDomain>());
|
||||
|
||||
sutProvider.GetDependency<IDnsResolverService>()
|
||||
.ResolveAsync(expected.DomainName, Arg.Any<string>())
|
||||
.Returns(false);
|
||||
@ -143,7 +167,7 @@ public class VerifyOrganizationDomainCommandTests
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UserVerifyOrganizationDomainAsync_GivenOrganizationDomainWithAccountDeprovisioningEnabled_WhenDomainIsVerified_ThenSingleOrgPolicyShouldBeEnabled(
|
||||
OrganizationDomain domain, SutProvider<VerifyOrganizationDomainCommand> sutProvider)
|
||||
OrganizationDomain domain, Guid userId, SutProvider<VerifyOrganizationDomainCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetClaimedDomainsByDomainNameAsync(domain.DomainName)
|
||||
@ -157,11 +181,18 @@ public class VerifyOrganizationDomainCommandTests
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(userId);
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyService>()
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>()
|
||||
.Received(1)
|
||||
.SaveAsync(Arg.Is<Policy>(x => x.Type == PolicyType.SingleOrg && x.OrganizationId == domain.OrganizationId && x.Enabled), null);
|
||||
.SaveAsync(Arg.Is<PolicyUpdate>(x => x.Type == PolicyType.SingleOrg &&
|
||||
x.OrganizationId == domain.OrganizationId &&
|
||||
x.Enabled &&
|
||||
x.PerformedBy is StandardUser &&
|
||||
x.PerformedBy.UserId == userId));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -176,20 +207,23 @@ public class VerifyOrganizationDomainCommandTests
|
||||
.ResolveAsync(domain.DomainName, domain.Txt)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(false);
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyService>()
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>()
|
||||
.DidNotReceive()
|
||||
.SaveAsync(Arg.Any<Policy>(), null);
|
||||
.SaveAsync(Arg.Any<PolicyUpdate>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UserVerifyOrganizationDomainAsync_GivenOrganizationDomainWithAccountDeprovisioningEnabled_WhenDomainIsNotVerified_ThenSingleOrgPolicyShouldNotBeEnabled(
|
||||
OrganizationDomain domain, SutProvider<VerifyOrganizationDomainCommand> sutProvider)
|
||||
OrganizationDomain domain, SutProvider<VerifyOrganizationDomainCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetClaimedDomainsByDomainNameAsync(domain.DomainName)
|
||||
@ -199,16 +233,18 @@ public class VerifyOrganizationDomainCommandTests
|
||||
.ResolveAsync(domain.DomainName, domain.Txt)
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyService>()
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>()
|
||||
.DidNotReceive()
|
||||
.SaveAsync(Arg.Any<Policy>(), null);
|
||||
|
||||
.SaveAsync(Arg.Any<PolicyUpdate>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -223,14 +259,66 @@ public class VerifyOrganizationDomainCommandTests
|
||||
.ResolveAsync(domain.DomainName, domain.Txt)
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyService>()
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>()
|
||||
.DidNotReceive()
|
||||
.SaveAsync(Arg.Any<Policy>(), null);
|
||||
.SaveAsync(Arg.Any<PolicyUpdate>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UserVerifyOrganizationDomainAsync_GivenOrganizationDomainWithAccountDeprovisioningEnabled_WhenDomainIsVerified_ThenEmailShouldBeSentToUsersWhoBelongToTheDomain(
|
||||
ICollection<OrganizationUserUserDetails> organizationUsers,
|
||||
OrganizationDomain domain,
|
||||
Organization organization,
|
||||
SutProvider<VerifyOrganizationDomainCommand> sutProvider)
|
||||
{
|
||||
foreach (var organizationUser in organizationUsers)
|
||||
{
|
||||
organizationUser.Email = $"{organizationUser.Name}@{domain.DomainName}";
|
||||
}
|
||||
|
||||
var mockedUsers = organizationUsers
|
||||
.Where(x => x.Status != OrganizationUserStatusType.Invited &&
|
||||
x.Status != OrganizationUserStatusType.Revoked).ToList();
|
||||
|
||||
organization.Id = domain.OrganizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetClaimedDomainsByDomainNameAsync(domain.DomainName)
|
||||
.Returns([]);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(domain.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IDnsResolverService>()
|
||||
.ResolveAsync(domain.DomainName, domain.Txt)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(domain.OrganizationId)
|
||||
.Returns(mockedUsers);
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().Received().SendClaimedDomainUserEmailAsync(
|
||||
Arg.Is<ManagedUserDomainClaimedEmails>(x =>
|
||||
x.EmailList.Count(e => e.EndsWith(domain.DomainName)) == mockedUsers.Count &&
|
||||
x.Organization.Id == organization.Id));
|
||||
}
|
||||
}
|
||||
|
@ -258,14 +258,15 @@ public class DeleteManagedOrganizationUserAccountCommandTests
|
||||
.Returns(new Dictionary<Guid, bool> { { orgUser1.Id, true }, { orgUser2.Id, true } });
|
||||
|
||||
// Act
|
||||
var results = await sutProvider.Sut.DeleteManyUsersAsync(organizationId, new[] { orgUser1.Id, orgUser2.Id }, null);
|
||||
var userIds = new[] { orgUser1.Id, orgUser2.Id };
|
||||
var results = await sutProvider.Sut.DeleteManyUsersAsync(organizationId, userIds, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, results.Count());
|
||||
Assert.All(results, r => Assert.Empty(r.Item2));
|
||||
|
||||
await sutProvider.GetDependency<IUserService>().Received(1).DeleteAsync(user1);
|
||||
await sutProvider.GetDependency<IUserService>().Received(1).DeleteAsync(user2);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyAsync(userIds);
|
||||
await sutProvider.GetDependency<IUserRepository>().Received(1).DeleteManyAsync(Arg.Is<IEnumerable<User>>(users => users.Any(u => u.Id == user1.Id) && users.Any(u => u.Id == user2.Id)));
|
||||
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(
|
||||
Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(events =>
|
||||
events.Count(e => e.Item1.Id == orgUser1.Id && e.Item2 == EventType.OrganizationUser_Deleted) == 1
|
||||
@ -286,7 +287,9 @@ public class DeleteManagedOrganizationUserAccountCommandTests
|
||||
Assert.Single(result);
|
||||
Assert.Equal(orgUserId, result.First().Item1);
|
||||
Assert.Contains("Member not found.", result.First().Item2);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IUserRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, DateTime?)>>());
|
||||
}
|
||||
@ -484,7 +487,6 @@ public class DeleteManagedOrganizationUserAccountCommandTests
|
||||
Assert.Equal("You cannot delete a member with Invited status.", results.First(r => r.Item1 == orgUser2.Id).Item2);
|
||||
Assert.Equal("Member is not managed by the organization.", results.First(r => r.Item1 == orgUser3.Id).Item2);
|
||||
|
||||
await sutProvider.GetDependency<IUserService>().Received(1).DeleteAsync(user1);
|
||||
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(
|
||||
Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(events =>
|
||||
events.Count(e => e.Item1.Id == orgUser1.Id && e.Item2 == EventType.OrganizationUser_Deleted) == 1));
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,185 @@
|
||||
using Bit.Core.AdminConsole.Models.Data;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class RevokeNonCompliantOrganizationUserCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenUnrecognizedUserType_WhenAttemptingToRevoke_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, [], new InvalidUser());
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.Contains(RevokeNonCompliantOrganizationUserCommand.ErrorRequestedByWasNotValid, result.ErrorMessages);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenPopulatedRequest_WhenUserAttemptsToRevokeThemselves_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, OrganizationUserUserDetails revokingUser,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, revokingUser,
|
||||
new StandardUser(revokingUser?.UserId ?? Guid.NewGuid(), true));
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.Contains(RevokeNonCompliantOrganizationUserCommand.ErrorCannotRevokeSelf, result.ErrorMessages);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenPopulatedRequest_WhenUserAttemptsToRevokeOrgUsersFromAnotherOrg_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, OrganizationUserUserDetails userFromAnotherOrg,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
userFromAnotherOrg.OrganizationId = Guid.NewGuid();
|
||||
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, userFromAnotherOrg,
|
||||
new StandardUser(Guid.NewGuid(), true));
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.Contains(RevokeNonCompliantOrganizationUserCommand.ErrorInvalidUsers, result.ErrorMessages);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenPopulatedRequest_WhenUserAttemptsToRevokeAllOwnersFromOrg_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, OrganizationUserUserDetails userToRevoke,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
userToRevoke.OrganizationId = organizationId;
|
||||
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, userToRevoke,
|
||||
new StandardUser(Guid.NewGuid(), true));
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(false);
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.Contains(RevokeNonCompliantOrganizationUserCommand.ErrorOrgMustHaveAtLeastOneOwner, result.ErrorMessages);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenPopulatedRequest_WhenUserAttemptsToRevokeOwnerWhenNotAnOwner_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, OrganizationUserUserDetails userToRevoke,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
userToRevoke.OrganizationId = organizationId;
|
||||
userToRevoke.Type = OrganizationUserType.Owner;
|
||||
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, userToRevoke,
|
||||
new StandardUser(Guid.NewGuid(), false));
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.Contains(RevokeNonCompliantOrganizationUserCommand.ErrorOnlyOwnersCanRevokeOtherOwners, result.ErrorMessages);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenPopulatedRequest_WhenUserAttemptsToRevokeUserWhoIsAlreadyRevoked_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, OrganizationUserUserDetails userToRevoke,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
userToRevoke.OrganizationId = organizationId;
|
||||
userToRevoke.Status = OrganizationUserStatusType.Revoked;
|
||||
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, userToRevoke,
|
||||
new StandardUser(Guid.NewGuid(), true));
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.Contains($"{RevokeNonCompliantOrganizationUserCommand.ErrorUserAlreadyRevoked} Id: {userToRevoke.Id}", result.ErrorMessages);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenPopulatedRequest_WhenUserHasMultipleInvalidUsers_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, IEnumerable<OrganizationUserUserDetails> usersToRevoke,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
var revocableUsers = usersToRevoke.ToList();
|
||||
revocableUsers.ForEach(user => user.OrganizationId = organizationId);
|
||||
revocableUsers[0].Type = OrganizationUserType.Owner;
|
||||
revocableUsers[1].Status = OrganizationUserStatusType.Revoked;
|
||||
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, revocableUsers,
|
||||
new StandardUser(Guid.NewGuid(), false));
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.True(result.ErrorMessages.Count > 1);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenValidPopulatedRequest_WhenUserAttemptsToRevokeAUser_ThenUserShouldBeRevoked(
|
||||
Guid organizationId, OrganizationUserUserDetails userToRevoke,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
userToRevoke.OrganizationId = organizationId;
|
||||
userToRevoke.Type = OrganizationUserType.Admin;
|
||||
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, userToRevoke,
|
||||
new StandardUser(Guid.NewGuid(), false));
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.RevokeManyByIdAsync(Arg.Any<IEnumerable<Guid>>());
|
||||
|
||||
Assert.True(result.Success);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogOrganizationUserEventsAsync(
|
||||
Arg.Is<IEnumerable<(OrganizationUserUserDetails organizationUser, EventType eventType, DateTime? time
|
||||
)>>(
|
||||
x => x.Any(y =>
|
||||
y.organizationUser.Id == userToRevoke.Id && y.eventType == EventType.OrganizationUser_Revoked)
|
||||
));
|
||||
}
|
||||
|
||||
public class InvalidUser : IActingUser
|
||||
{
|
||||
public Guid? UserId => Guid.Empty;
|
||||
public bool IsOrganizationOwnerOrProvider => false;
|
||||
public EventSystemUser? SystemUserType => null;
|
||||
}
|
||||
}
|
@ -14,34 +14,13 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
public class UpdateOrganizationUserGroupsCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateUserGroups_Passes(
|
||||
public async Task UpdateUserGroups_ShouldUpdateUserGroupsAndLogUserEvent(
|
||||
OrganizationUser organizationUser,
|
||||
IEnumerable<Guid> groupIds,
|
||||
SutProvider<UpdateOrganizationUserGroupsCommand> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.UpdateUserGroupsAsync(organizationUser, groupIds, null);
|
||||
await sutProvider.Sut.UpdateUserGroupsAsync(organizationUser, groupIds);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationService>().DidNotReceiveWithAnyArgs()
|
||||
.ValidateOrganizationUserUpdatePermissions(default, default, default, default);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
|
||||
.UpdateGroupsAsync(organizationUser.Id, groupIds);
|
||||
await sutProvider.GetDependency<IEventService>().Received(1)
|
||||
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UpdatedGroups);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateUserGroups_WithSavingUserId_Passes(
|
||||
OrganizationUser organizationUser,
|
||||
IEnumerable<Guid> groupIds,
|
||||
Guid savingUserId,
|
||||
SutProvider<UpdateOrganizationUserGroupsCommand> sutProvider)
|
||||
{
|
||||
organizationUser.Permissions = null;
|
||||
|
||||
await sutProvider.Sut.UpdateUserGroupsAsync(organizationUser, groupIds, savingUserId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationService>().Received(1)
|
||||
.ValidateOrganizationUserUpdatePermissions(organizationUser.OrganizationId, organizationUser.Type, null, organizationUser.GetPermissions());
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
|
||||
.UpdateGroupsAsync(organizationUser.Id, groupIds);
|
||||
await sutProvider.GetDependency<IEventService>().Received(1)
|
||||
|
@ -0,0 +1,238 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tools.Enums;
|
||||
using Bit.Core.Tools.Models.Business;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations.OrganizationSignUp;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class CloudICloudOrganizationSignUpCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||
public async Task SignUp_PM_Family_Passes(PlanType planType, OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
|
||||
signup.AdditionalSeats = 0;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.UseSecretsManager = false;
|
||||
signup.IsFromSecretsManagerTrial = false;
|
||||
|
||||
var result = await sutProvider.Sut.SignUpOrganizationAsync(signup);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<Organization>(o =>
|
||||
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
|
||||
&& o.SmSeats == null
|
||||
&& o.SmServiceAccounts == null));
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<OrganizationUser>(o => o.AccessSecretsManager == signup.UseSecretsManager));
|
||||
|
||||
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
|
||||
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
|
||||
referenceEvent.Type == ReferenceEventType.Signup &&
|
||||
referenceEvent.PlanName == plan.Name &&
|
||||
referenceEvent.PlanType == plan.Type &&
|
||||
referenceEvent.Seats == result.Organization.Seats &&
|
||||
referenceEvent.Storage == result.Organization.MaxStorageGb));
|
||||
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
|
||||
|
||||
Assert.NotNull(result.Organization);
|
||||
Assert.NotNull(result.OrganizationUser);
|
||||
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
|
||||
Arg.Any<Organization>(),
|
||||
signup.PaymentMethodType.Value,
|
||||
signup.PaymentToken,
|
||||
plan,
|
||||
signup.AdditionalStorageGb,
|
||||
signup.AdditionalSeats,
|
||||
signup.PremiumAccessAddon,
|
||||
signup.TaxInfo,
|
||||
false,
|
||||
signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
||||
signup.UseSecretsManager
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||
public async Task SignUp_AssignsOwnerToDefaultCollection
|
||||
(PlanType planType, OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
signup.AdditionalSeats = 0;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.UseSecretsManager = false;
|
||||
|
||||
// Extract orgUserId when created
|
||||
Guid? orgUserId = null;
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.CreateAsync(Arg.Do<OrganizationUser>(ou => orgUserId = ou.Id));
|
||||
|
||||
var result = await sutProvider.Sut.SignUpOrganizationAsync(signup);
|
||||
|
||||
// Assert: created a Can Manage association for the default collection
|
||||
Assert.NotNull(orgUserId);
|
||||
await sutProvider.GetDependency<ICollectionRepository>().Received(1).CreateAsync(
|
||||
Arg.Any<Collection>(),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(cas => cas == null),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(cas =>
|
||||
cas.Count() == 1 &&
|
||||
cas.All(c =>
|
||||
c.Id == orgUserId &&
|
||||
!c.ReadOnly &&
|
||||
!c.HidePasswords &&
|
||||
c.Manage)));
|
||||
|
||||
Assert.NotNull(result.Organization);
|
||||
Assert.NotNull(result.OrganizationUser);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||
[BitAutoData(PlanType.EnterpriseMonthly)]
|
||||
[BitAutoData(PlanType.TeamsAnnually)]
|
||||
[BitAutoData(PlanType.TeamsMonthly)]
|
||||
public async Task SignUp_SM_Passes(PlanType planType, OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
|
||||
signup.UseSecretsManager = true;
|
||||
signup.AdditionalSeats = 15;
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalServiceAccounts = 20;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.IsFromSecretsManagerTrial = false;
|
||||
|
||||
var result = await sutProvider.Sut.SignUpOrganizationAsync(signup);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<Organization>(o =>
|
||||
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
|
||||
&& o.SmSeats == plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats
|
||||
&& o.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + signup.AdditionalServiceAccounts));
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<OrganizationUser>(o => o.AccessSecretsManager == signup.UseSecretsManager));
|
||||
|
||||
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
|
||||
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
|
||||
referenceEvent.Type == ReferenceEventType.Signup &&
|
||||
referenceEvent.PlanName == plan.Name &&
|
||||
referenceEvent.PlanType == plan.Type &&
|
||||
referenceEvent.Seats == result.Organization.Seats &&
|
||||
referenceEvent.Storage == result.Organization.MaxStorageGb));
|
||||
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
|
||||
|
||||
Assert.NotNull(result.Organization);
|
||||
Assert.NotNull(result.OrganizationUser);
|
||||
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
|
||||
Arg.Any<Organization>(),
|
||||
signup.PaymentMethodType.Value,
|
||||
signup.PaymentToken,
|
||||
Arg.Is<Plan>(plan),
|
||||
signup.AdditionalStorageGb,
|
||||
signup.AdditionalSeats,
|
||||
signup.PremiumAccessAddon,
|
||||
signup.TaxInfo,
|
||||
false,
|
||||
signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
||||
signup.IsFromSecretsManagerTrial
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||
public async Task SignUp_SM_Throws_WhenManagedByMSP(PlanType planType, OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.AdditionalSeats = 15;
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalServiceAccounts = 20;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.IsFromProvider = true;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||
Assert.Contains("Organizations with a Managed Service Provider do not support Secrets Manager.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_SecretManager_AdditionalServiceAccounts_NotAllowedByPlan_ShouldThrowException(OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 0;
|
||||
signup.AdditionalSeats = 0;
|
||||
signup.Plan = PlanType.Free;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = 10;
|
||||
signup.AdditionalStorageGb = 0;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||
Assert.Contains("Plan does not allow additional Machine Accounts.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_SMSeatsGreatThanPMSeat_ShouldThrowException(OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 100;
|
||||
signup.AdditionalSeats = 10;
|
||||
signup.Plan = PlanType.EnterpriseAnnually;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = 10;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||
Assert.Contains("You cannot have more Secrets Manager seats than Password Manager seats", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_InvalidateServiceAccount_ShouldThrowException(OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalSeats = 10;
|
||||
signup.Plan = PlanType.EnterpriseAnnually;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = -10;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||
Assert.Contains("You can't subtract Machine Accounts!", exception.Message);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
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.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.Auth.Entities;
|
||||
@ -10,6 +11,7 @@ using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Commands;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@ -61,6 +63,92 @@ public class SingleOrgPolicyValidatorTests
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_RevokesNonCompliantUsers(
|
||||
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg, false)] Policy policy,
|
||||
Guid savingUserId,
|
||||
Guid nonCompliantUserId,
|
||||
Organization organization, SutProvider<SingleOrgPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
|
||||
var compliantUser1 = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user1@example.com"
|
||||
};
|
||||
|
||||
var compliantUser2 = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user2@example.com"
|
||||
};
|
||||
|
||||
var nonCompliantUser = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = nonCompliantUserId,
|
||||
Email = "user3@example.com"
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([compliantUser1, compliantUser2, nonCompliantUser]);
|
||||
|
||||
var otherOrganizationUser = new OrganizationUser
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = new Guid(),
|
||||
UserId = nonCompliantUserId,
|
||||
Status = OrganizationUserStatusType.Confirmed
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByManyUsersAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(nonCompliantUserId)))
|
||||
.Returns([otherOrganizationUser]);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(savingUserId);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(policyUpdate.OrganizationId).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>())
|
||||
.Returns(new CommandResult());
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RevokeNonCompliantOrganizationUsersAsync(
|
||||
Arg.Is<RevokeOrganizationUsersRequest>(r =>
|
||||
r.OrganizationId == organization.Id &&
|
||||
r.OrganizationUsers.Count() == 1 &&
|
||||
r.OrganizationUsers.First().Id == nonCompliantUser.Id));
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), compliantUser1.Email);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), compliantUser2.Email);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), nonCompliantUser.Email);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_RemovesNonCompliantUsers(
|
||||
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||
@ -71,6 +159,94 @@ public class SingleOrgPolicyValidatorTests
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
|
||||
var compliantUser1 = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user1@example.com"
|
||||
};
|
||||
|
||||
var compliantUser2 = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user2@example.com"
|
||||
};
|
||||
|
||||
var nonCompliantUser = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = nonCompliantUserId,
|
||||
Email = "user3@example.com"
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([compliantUser1, compliantUser2, nonCompliantUser]);
|
||||
|
||||
var otherOrganizationUser = new OrganizationUser
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = new Guid(),
|
||||
UserId = nonCompliantUserId,
|
||||
Status = OrganizationUserStatusType.Confirmed
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByManyUsersAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(nonCompliantUserId)))
|
||||
.Returns([otherOrganizationUser]);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(savingUserId);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(policyUpdate.OrganizationId).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>())
|
||||
.Returns(new CommandResult());
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.DidNotReceive()
|
||||
.RemoveUserAsync(policyUpdate.OrganizationId, compliantUser1.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.DidNotReceive()
|
||||
.RemoveUserAsync(policyUpdate.OrganizationId, compliantUser2.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RemoveUserAsync(policyUpdate.OrganizationId, nonCompliantUser.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(organization.DisplayName(), compliantUser1.Email);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(organization.DisplayName(), compliantUser2.Email);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(organization.DisplayName(), nonCompliantUser.Email);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_WhenAccountDeprovisioningIsEnabled_ThenUsersAreRevoked(
|
||||
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg, false)] Policy policy,
|
||||
Guid savingUserId,
|
||||
Guid nonCompliantUserId,
|
||||
Organization organization, SutProvider<SingleOrgPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
|
||||
var compliantUser1 = new OrganizationUserUserDetails
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
@ -114,16 +290,19 @@ public class SingleOrgPolicyValidatorTests
|
||||
.Returns([otherOrganizationUser]);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(savingUserId);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(policyUpdate.OrganizationId).Returns(organization);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>())
|
||||
.Returns(new CommandResult());
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RemoveUserAsync(policyUpdate.OrganizationId, nonCompliantUser.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(organization.DisplayName(),
|
||||
"user3@example.com");
|
||||
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.Received()
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>());
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
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.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Commands;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@ -176,6 +178,10 @@ public class TwoFactorAuthenticationPolicyValidatorTests
|
||||
HasMasterPassword = false
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policy.OrganizationId)
|
||||
.Returns(new List<OrganizationUserUserDetails>
|
||||
@ -201,9 +207,151 @@ public class TwoFactorAuthenticationPolicyValidatorTests
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy));
|
||||
|
||||
Assert.Contains("Policy could not be enabled. Non-compliant members will lose access to their accounts. Identify members without two-step login from the policies column in the members page.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Equal(TwoFactorAuthenticationPolicyValidator.NonCompliantMembersWillLoseAccessMessage, badRequestException.Message);
|
||||
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>().DidNotReceiveWithAnyArgs()
|
||||
.RemoveUserAsync(organizationId: default, organizationUserId: default, deletingUserId: default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_GivenUpdateTo2faPolicy_WhenAccountProvisioningIsDisabled_ThenRevokeUserCommandShouldNotBeCalled(
|
||||
Organization organization,
|
||||
[PolicyUpdate(PolicyType.TwoFactorAuthentication)]
|
||||
PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.TwoFactorAuthentication, false)]
|
||||
Policy policy,
|
||||
SutProvider<TwoFactorAuthenticationPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(false);
|
||||
|
||||
var orgUserDetailUserAcceptedWithout2Fa = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
Type = OrganizationUserType.User,
|
||||
Email = "user3@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns(new List<OrganizationUserUserDetails>
|
||||
{
|
||||
orgUserDetailUserAcceptedWithout2Fa
|
||||
});
|
||||
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<OrganizationUserUserDetails>>())
|
||||
.Returns(new List<(OrganizationUserUserDetails user, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetailUserAcceptedWithout2Fa, false),
|
||||
});
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.DidNotReceive()
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_GivenUpdateTo2faPolicy_WhenAccountProvisioningIsEnabledAndUserDoesNotHaveMasterPassword_ThenNonCompliantMembersErrorMessageWillReturn(
|
||||
Organization organization,
|
||||
[PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy,
|
||||
SutProvider<TwoFactorAuthenticationPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
var orgUserDetailUserWithout2Fa = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.User,
|
||||
Email = "user3@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([orgUserDetailUserWithout2Fa]);
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<OrganizationUserUserDetails>>())
|
||||
.Returns(new List<(OrganizationUserUserDetails user, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetailUserWithout2Fa, false),
|
||||
});
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy));
|
||||
|
||||
Assert.Equal(TwoFactorAuthenticationPolicyValidator.NonCompliantMembersWillLoseAccessMessage, exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_WhenAccountProvisioningIsEnabledAndUserHasMasterPassword_ThenUserWillBeRevoked(
|
||||
Organization organization,
|
||||
[PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy,
|
||||
SutProvider<TwoFactorAuthenticationPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
var orgUserDetailUserWithout2Fa = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.User,
|
||||
Email = "user3@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([orgUserDetailUserWithout2Fa]);
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<OrganizationUserUserDetails>>())
|
||||
.Returns(new List<(OrganizationUserUserDetails user, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetailUserWithout2Fa, true),
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>())
|
||||
.Returns(new CommandResult());
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>());
|
||||
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(),
|
||||
"user3@test.com");
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ public class SavePolicyCommandTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest(PolicyUpdate policyUpdate)
|
||||
public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest([PolicyUpdate(PolicyType.ActivateAutofill)] PolicyUpdate policyUpdate)
|
||||
{
|
||||
var sutProvider = SutProviderFactory();
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
@ -115,7 +115,7 @@ public class SavePolicyCommandTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest(PolicyUpdate policyUpdate)
|
||||
public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest([PolicyUpdate(PolicyType.ActivateAutofill)] PolicyUpdate policyUpdate)
|
||||
{
|
||||
var sutProvider = SutProviderFactory();
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
|
@ -169,7 +169,6 @@ public class EventServiceTests
|
||||
new EventMessage()
|
||||
{
|
||||
IpAddress = ipAddress,
|
||||
DeviceType = DeviceType.Server,
|
||||
OrganizationId = orgUser.OrganizationId,
|
||||
UserId = orgUser.UserId,
|
||||
OrganizationUserId = orgUser.Id,
|
||||
|
@ -20,7 +20,6 @@ using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Models.Mail;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@ -200,221 +199,6 @@ public class OrganizationServiceTests
|
||||
referenceEvent.Users == expectedNewUsersCount));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||
public async Task SignUp_PM_Family_Passes(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
|
||||
signup.AdditionalSeats = 0;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.UseSecretsManager = false;
|
||||
signup.IsFromSecretsManagerTrial = false;
|
||||
|
||||
var purchaseOrganizationPlan = StaticStore.GetPlan(signup.Plan);
|
||||
|
||||
var result = await sutProvider.Sut.SignUpAsync(signup);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<Organization>(o =>
|
||||
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
|
||||
&& o.SmSeats == null
|
||||
&& o.SmServiceAccounts == null));
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<OrganizationUser>(o => o.AccessSecretsManager == signup.UseSecretsManager));
|
||||
|
||||
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
|
||||
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
|
||||
referenceEvent.Type == ReferenceEventType.Signup &&
|
||||
referenceEvent.PlanName == plan.Name &&
|
||||
referenceEvent.PlanType == plan.Type &&
|
||||
referenceEvent.Seats == result.Item1.Seats &&
|
||||
referenceEvent.Storage == result.Item1.MaxStorageGb));
|
||||
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
|
||||
|
||||
Assert.NotNull(result.Item1);
|
||||
Assert.NotNull(result.Item2);
|
||||
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
|
||||
Arg.Any<Organization>(),
|
||||
signup.PaymentMethodType.Value,
|
||||
signup.PaymentToken,
|
||||
plan,
|
||||
signup.AdditionalStorageGb,
|
||||
signup.AdditionalSeats,
|
||||
signup.PremiumAccessAddon,
|
||||
signup.TaxInfo,
|
||||
false,
|
||||
signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
||||
signup.UseSecretsManager
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||
public async Task SignUp_AssignsOwnerToDefaultCollection
|
||||
(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
signup.AdditionalSeats = 0;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.UseSecretsManager = false;
|
||||
|
||||
// Extract orgUserId when created
|
||||
Guid? orgUserId = null;
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.CreateAsync(Arg.Do<OrganizationUser>(ou => orgUserId = ou.Id));
|
||||
|
||||
var result = await sutProvider.Sut.SignUpAsync(signup);
|
||||
|
||||
// Assert: created a Can Manage association for the default collection
|
||||
Assert.NotNull(orgUserId);
|
||||
await sutProvider.GetDependency<ICollectionRepository>().Received(1).CreateAsync(
|
||||
Arg.Any<Collection>(),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(cas => cas == null),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(cas =>
|
||||
cas.Count() == 1 &&
|
||||
cas.All(c =>
|
||||
c.Id == orgUserId &&
|
||||
!c.ReadOnly &&
|
||||
!c.HidePasswords &&
|
||||
c.Manage)));
|
||||
|
||||
Assert.NotNull(result.Item1);
|
||||
Assert.NotNull(result.Item2);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||
[BitAutoData(PlanType.EnterpriseMonthly)]
|
||||
[BitAutoData(PlanType.TeamsAnnually)]
|
||||
[BitAutoData(PlanType.TeamsMonthly)]
|
||||
public async Task SignUp_SM_Passes(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
|
||||
signup.UseSecretsManager = true;
|
||||
signup.AdditionalSeats = 15;
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalServiceAccounts = 20;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.IsFromSecretsManagerTrial = false;
|
||||
|
||||
var result = await sutProvider.Sut.SignUpAsync(signup);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<Organization>(o =>
|
||||
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
|
||||
&& o.SmSeats == plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats
|
||||
&& o.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + signup.AdditionalServiceAccounts));
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<OrganizationUser>(o => o.AccessSecretsManager == signup.UseSecretsManager));
|
||||
|
||||
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
|
||||
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
|
||||
referenceEvent.Type == ReferenceEventType.Signup &&
|
||||
referenceEvent.PlanName == plan.Name &&
|
||||
referenceEvent.PlanType == plan.Type &&
|
||||
referenceEvent.Seats == result.Item1.Seats &&
|
||||
referenceEvent.Storage == result.Item1.MaxStorageGb));
|
||||
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
|
||||
|
||||
Assert.NotNull(result.Item1);
|
||||
Assert.NotNull(result.Item2);
|
||||
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
|
||||
Arg.Any<Organization>(),
|
||||
signup.PaymentMethodType.Value,
|
||||
signup.PaymentToken,
|
||||
Arg.Is<Plan>(plan),
|
||||
signup.AdditionalStorageGb,
|
||||
signup.AdditionalSeats,
|
||||
signup.PremiumAccessAddon,
|
||||
signup.TaxInfo,
|
||||
false,
|
||||
signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
||||
signup.IsFromSecretsManagerTrial
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||
public async Task SignUp_SM_Throws_WhenManagedByMSP(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.AdditionalSeats = 15;
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalServiceAccounts = 20;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.IsFromProvider = true;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SignUpAsync(signup));
|
||||
Assert.Contains("Organizations with a Managed Service Provider do not support Secrets Manager.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_SecretManager_AdditionalServiceAccounts_NotAllowedByPlan_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 0;
|
||||
signup.AdditionalSeats = 0;
|
||||
signup.Plan = PlanType.Free;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = 10;
|
||||
signup.AdditionalStorageGb = 0;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpAsync(signup));
|
||||
Assert.Contains("Plan does not allow additional Machine Accounts.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_SMSeatsGreatThanPMSeat_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 100;
|
||||
signup.AdditionalSeats = 10;
|
||||
signup.Plan = PlanType.EnterpriseAnnually;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = 10;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpAsync(signup));
|
||||
Assert.Contains("You cannot have more Secrets Manager seats than Password Manager seats", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_InvalidateServiceAccount_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalSeats = 10;
|
||||
signup.Plan = PlanType.EnterpriseAnnually;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = -10;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpAsync(signup));
|
||||
Assert.Contains("You can't subtract Machine Accounts!", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SignupClientAsync_Succeeds(
|
||||
OrganizationSignup signup,
|
||||
@ -1833,11 +1617,14 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
||||
.AnyPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any<OrganizationUserStatusType>())
|
||||
.Returns(true);
|
||||
|
||||
var user = new User();
|
||||
user.Email = "test@bitwarden.com";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||
|
||||
Assert.Contains("you cannot restore this user because they are a member of " +
|
||||
"another organization which forbids it", exception.Message.ToLowerInvariant());
|
||||
Assert.Contains("test@bitwarden.com belongs to an organization that doesn't allow them to join multiple organizations", exception.Message.ToLowerInvariant());
|
||||
|
||||
await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
||||
await eventService.DidNotReceiveWithAnyArgs()
|
||||
@ -1865,11 +1652,14 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
||||
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any<OrganizationUserStatusType>())
|
||||
.Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication } });
|
||||
|
||||
var user = new User();
|
||||
user.Email = "test@bitwarden.com";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||
|
||||
Assert.Contains("you cannot restore this user until they enable " +
|
||||
"two-step login on their user account.", exception.Message.ToLowerInvariant());
|
||||
Assert.Contains("test@bitwarden.com is not compliant with the two-step login policy", exception.Message.ToLowerInvariant());
|
||||
|
||||
await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
||||
await eventService.DidNotReceiveWithAnyArgs()
|
||||
@ -1924,11 +1714,14 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
||||
new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.SingleOrg, OrganizationUserStatus = OrganizationUserStatusType.Revoked }
|
||||
});
|
||||
|
||||
var user = new User();
|
||||
user.Email = "test@bitwarden.com";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||
|
||||
Assert.Contains("you cannot restore this user until " +
|
||||
"they leave or remove all other organizations.", exception.Message.ToLowerInvariant());
|
||||
Assert.Contains("test@bitwarden.com is not compliant with the single organization policy", exception.Message.ToLowerInvariant());
|
||||
|
||||
await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
||||
await eventService.DidNotReceiveWithAnyArgs()
|
||||
@ -1958,11 +1751,57 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
||||
.AnyPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any<OrganizationUserStatusType>())
|
||||
.Returns(true);
|
||||
|
||||
var user = new User();
|
||||
user.Email = "test@bitwarden.com";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||
|
||||
Assert.Contains("you cannot restore this user because they are a member of " +
|
||||
"another organization which forbids it", exception.Message.ToLowerInvariant());
|
||||
Assert.Contains("test@bitwarden.com belongs to an organization that doesn't allow them to join multiple organizations", exception.Message.ToLowerInvariant());
|
||||
|
||||
await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
||||
await eventService.DidNotReceiveWithAnyArgs()
|
||||
.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>(), Arg.Any<EventSystemUser>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RestoreUser_WithSingleOrgPolicyEnabled_And_2FA_Policy_Fails(
|
||||
Organization organization,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
||||
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser,
|
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser secondOrganizationUser,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke
|
||||
secondOrganizationUser.UserId = organizationUser.UserId;
|
||||
RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider);
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
var eventService = sutProvider.GetDependency<IEventService>();
|
||||
|
||||
organizationUserRepository.GetManyByUserAsync(organizationUser.UserId.Value).Returns(new[] { organizationUser, secondOrganizationUser });
|
||||
sutProvider.GetDependency<IPolicyService>()
|
||||
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any<OrganizationUserStatusType>())
|
||||
.Returns(new[]
|
||||
{
|
||||
new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.SingleOrg, OrganizationUserStatus = OrganizationUserStatusType.Revoked }
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyService>()
|
||||
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any<OrganizationUserStatusType>())
|
||||
.Returns(new[]
|
||||
{
|
||||
new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication, OrganizationUserStatus = OrganizationUserStatusType.Revoked }
|
||||
});
|
||||
|
||||
var user = new User();
|
||||
user.Email = "test@bitwarden.com";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||
|
||||
Assert.Contains("test@bitwarden.com is not compliant with the single organization and two-step login polciy", exception.Message.ToLowerInvariant());
|
||||
|
||||
await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
||||
await eventService.DidNotReceiveWithAnyArgs()
|
||||
@ -1986,11 +1825,14 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
||||
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any<OrganizationUserStatusType>())
|
||||
.Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication } });
|
||||
|
||||
var user = new User();
|
||||
user.Email = "test@bitwarden.com";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||
|
||||
Assert.Contains("you cannot restore this user until they enable " +
|
||||
"two-step login on their user account.", exception.Message.ToLowerInvariant());
|
||||
Assert.Contains("test@bitwarden.com is not compliant with the two-step login policy", exception.Message.ToLowerInvariant());
|
||||
|
||||
await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
||||
await eventService.DidNotReceiveWithAnyArgs()
|
||||
|
@ -1,25 +1,13 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services.Implementations;
|
||||
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.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using AdminConsoleFixtures = Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
using GlobalSettings = Bit.Core.Settings.GlobalSettings;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.Services;
|
||||
@ -27,667 +15,6 @@ namespace Bit.Core.Test.AdminConsole.Services;
|
||||
[SutProviderCustomize]
|
||||
public class PolicyServiceTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.DisableSend)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
SetupOrg(sutProvider, policy.OrganizationId, null);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Organization not found", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.DisableSend)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
var orgId = Guid.NewGuid();
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
UsePolicies = false,
|
||||
});
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("cannot use policies", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_SingleOrg_RequireSsoEnabled_ThrowsBadRequest(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.SingleOrg)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = false;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.RequireSso)
|
||||
.Returns(Task.FromResult(new Policy { Enabled = true }));
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Single Sign-On Authentication policy is enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_SingleOrg_VaultTimeoutEnabled_ThrowsBadRequest([AdminConsoleFixtures.Policy(PolicyType.SingleOrg)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = false;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.MaximumVaultTimeout)
|
||||
.Returns(new Policy { Enabled = true });
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Maximum Vault Timeout policy is enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PolicyType.SingleOrg)]
|
||||
[BitAutoData(PolicyType.RequireSso)]
|
||||
public async Task SaveAsync_PolicyRequiredByKeyConnector_DisablePolicy_ThrowsBadRequest(
|
||||
PolicyType policyType,
|
||||
Policy policy,
|
||||
SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = false;
|
||||
policy.Type = policyType;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
var ssoConfig = new SsoConfig { Enabled = true };
|
||||
var data = new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector };
|
||||
ssoConfig.SetData(data);
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.GetByOrganizationIdAsync(policy.OrganizationId)
|
||||
.Returns(ssoConfig);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Key Connector is enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_RequireSsoPolicy_NotEnabled_ThrowsBadRequestAsync(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.RequireSso)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = true;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(Task.FromResult(new Policy { Enabled = false }));
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Single Organization policy not enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_NewPolicy_Created(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.ResetPassword)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Id = default;
|
||||
policy.Data = null;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(Task.FromResult(new Policy { Enabled = true }));
|
||||
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
await sutProvider.Sut.SaveAsync(policy, Guid.NewGuid());
|
||||
|
||||
await sutProvider.GetDependency<IEventService>().Received()
|
||||
.LogPolicyEventAsync(policy, EventType.Policy_Updated);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>().Received()
|
||||
.UpsertAsync(policy);
|
||||
|
||||
Assert.True(policy.CreationDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
Assert.True(policy.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_VaultTimeoutPolicy_NotEnabled_ThrowsBadRequestAsync(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.MaximumVaultTimeout)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = true;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(Task.FromResult(new Policy { Enabled = false }));
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Single Organization policy not enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_ExistingPolicy_UpdateTwoFactor(
|
||||
Organization organization,
|
||||
[AdminConsoleFixtures.Policy(PolicyType.TwoFactorAuthentication)] Policy policy,
|
||||
SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
// If the policy that this is updating isn't enabled then do some work now that the current one is enabled
|
||||
|
||||
organization.UsePolicies = true;
|
||||
policy.OrganizationId = organization.Id;
|
||||
|
||||
SetupOrg(sutProvider, organization.Id, organization);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByIdAsync(policy.Id)
|
||||
.Returns(new Policy
|
||||
{
|
||||
Id = policy.Id,
|
||||
Type = PolicyType.TwoFactorAuthentication,
|
||||
Enabled = false
|
||||
});
|
||||
|
||||
var orgUserDetailUserInvited = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Invited,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user1@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
var orgUserDetailUserAcceptedWith2FA = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user2@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
var orgUserDetailUserAcceptedWithout2FA = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user3@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
var orgUserDetailAdmin = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.Admin,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "admin@test.com",
|
||||
Name = "ADMIN",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policy.OrganizationId)
|
||||
.Returns(new List<OrganizationUserUserDetails>
|
||||
{
|
||||
orgUserDetailUserInvited,
|
||||
orgUserDetailUserAcceptedWith2FA,
|
||||
orgUserDetailUserAcceptedWithout2FA,
|
||||
orgUserDetailAdmin
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<OrganizationUserUserDetails>>())
|
||||
.Returns(new List<(OrganizationUserUserDetails user, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetailUserInvited, false),
|
||||
(orgUserDetailUserAcceptedWith2FA, true),
|
||||
(orgUserDetailUserAcceptedWithout2FA, false),
|
||||
(orgUserDetailAdmin, false),
|
||||
});
|
||||
|
||||
var removeOrganizationUserCommand = sutProvider.GetDependency<IRemoveOrganizationUserCommand>();
|
||||
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
var savingUserId = Guid.NewGuid();
|
||||
|
||||
await sutProvider.Sut.SaveAsync(policy, savingUserId);
|
||||
|
||||
await removeOrganizationUserCommand.Received()
|
||||
.RemoveUserAsync(policy.OrganizationId, orgUserDetailUserAcceptedWithout2FA.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>().Received()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailUserAcceptedWithout2FA.Email);
|
||||
|
||||
await removeOrganizationUserCommand.DidNotReceive()
|
||||
.RemoveUserAsync(policy.OrganizationId, orgUserDetailUserInvited.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailUserInvited.Email);
|
||||
await removeOrganizationUserCommand.DidNotReceive()
|
||||
.RemoveUserAsync(policy.OrganizationId, orgUserDetailUserAcceptedWith2FA.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailUserAcceptedWith2FA.Email);
|
||||
await removeOrganizationUserCommand.DidNotReceive()
|
||||
.RemoveUserAsync(policy.OrganizationId, orgUserDetailAdmin.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailAdmin.Email);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>().Received()
|
||||
.LogPolicyEventAsync(policy, EventType.Policy_Updated);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>().Received()
|
||||
.UpsertAsync(policy);
|
||||
|
||||
Assert.True(policy.CreationDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
Assert.True(policy.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_EnableTwoFactor_WithoutMasterPasswordOr2FA_ThrowsBadRequest(
|
||||
Organization organization,
|
||||
[AdminConsoleFixtures.Policy(PolicyType.TwoFactorAuthentication)] Policy policy,
|
||||
SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
organization.UsePolicies = true;
|
||||
policy.OrganizationId = organization.Id;
|
||||
|
||||
SetupOrg(sutProvider, organization.Id, organization);
|
||||
|
||||
var orgUserDetailUserWith2FAAndMP = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user1@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
var orgUserDetailUserWith2FANoMP = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user2@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
var orgUserDetailUserWithout2FA = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user3@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
var orgUserDetailAdmin = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.Admin,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "admin@test.com",
|
||||
Name = "ADMIN",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policy.OrganizationId)
|
||||
.Returns(new List<OrganizationUserUserDetails>
|
||||
{
|
||||
orgUserDetailUserWith2FAAndMP,
|
||||
orgUserDetailUserWith2FANoMP,
|
||||
orgUserDetailUserWithout2FA,
|
||||
orgUserDetailAdmin
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(ids =>
|
||||
ids.Contains(orgUserDetailUserWith2FANoMP.UserId.Value)
|
||||
&& ids.Contains(orgUserDetailUserWithout2FA.UserId.Value)
|
||||
&& ids.Contains(orgUserDetailAdmin.UserId.Value)))
|
||||
.Returns(new List<(Guid userId, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetailUserWith2FANoMP.UserId.Value, true),
|
||||
(orgUserDetailUserWithout2FA.UserId.Value, false),
|
||||
(orgUserDetailAdmin.UserId.Value, false),
|
||||
});
|
||||
|
||||
var removeOrganizationUserCommand = sutProvider.GetDependency<IRemoveOrganizationUserCommand>();
|
||||
|
||||
var savingUserId = Guid.NewGuid();
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy, savingUserId));
|
||||
|
||||
Assert.Contains("Policy could not be enabled. Non-compliant members will lose access to their accounts. Identify members without two-step login from the policies column in the members page.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await removeOrganizationUserCommand.DidNotReceiveWithAnyArgs()
|
||||
.RemoveUserAsync(organizationId: default, organizationUserId: default, deletingUserId: default);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceiveWithAnyArgs()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(default, default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_ExistingPolicy_UpdateSingleOrg(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.TwoFactorAuthentication)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
// If the policy that this is updating isn't enabled then do some work now that the current one is enabled
|
||||
|
||||
var org = new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
Name = "TEST",
|
||||
};
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, org);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByIdAsync(policy.Id)
|
||||
.Returns(new Policy
|
||||
{
|
||||
Id = policy.Id,
|
||||
Type = PolicyType.SingleOrg,
|
||||
Enabled = false,
|
||||
});
|
||||
|
||||
var orgUserDetail = new Core.Models.Data.Organizations.OrganizationUsers.OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "test@bitwarden.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policy.OrganizationId)
|
||||
.Returns(new List<Core.Models.Data.Organizations.OrganizationUsers.OrganizationUserUserDetails>
|
||||
{
|
||||
orgUserDetail,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(orgUserDetail.UserId.Value)))
|
||||
.Returns(new List<(Guid userId, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetail.UserId.Value, false),
|
||||
});
|
||||
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
var savingUserId = Guid.NewGuid();
|
||||
|
||||
await sutProvider.Sut.SaveAsync(policy, savingUserId);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>().Received()
|
||||
.LogPolicyEventAsync(policy, EventType.Policy_Updated);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>().Received()
|
||||
.UpsertAsync(policy);
|
||||
|
||||
Assert.True(policy.CreationDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
Assert.True(policy.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(true, false)]
|
||||
[BitAutoData(false, true)]
|
||||
[BitAutoData(false, false)]
|
||||
public async Task SaveAsync_ResetPasswordPolicyRequiredByTrustedDeviceEncryption_DisablePolicyOrDisableAutomaticEnrollment_ThrowsBadRequest(
|
||||
bool policyEnabled,
|
||||
bool autoEnrollEnabled,
|
||||
[AdminConsoleFixtures.Policy(PolicyType.ResetPassword)] Policy policy,
|
||||
SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = policyEnabled;
|
||||
policy.SetDataModel(new ResetPasswordDataModel
|
||||
{
|
||||
AutoEnrollEnabled = autoEnrollEnabled
|
||||
});
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
var ssoConfig = new SsoConfig { Enabled = true };
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption });
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.GetByOrganizationIdAsync(policy.OrganizationId)
|
||||
.Returns(ssoConfig);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Trusted device encryption is on and requires this policy.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_RequireSsoPolicyRequiredByTrustedDeviceEncryption_DisablePolicy_ThrowsBadRequest(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.RequireSso)] Policy policy,
|
||||
SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = false;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
var ssoConfig = new SsoConfig { Enabled = true };
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption });
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.GetByOrganizationIdAsync(policy.OrganizationId)
|
||||
.Returns(ssoConfig);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Trusted device encryption is on and requires this policy.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_PolicyRequiredForAccountRecovery_NotEnabled_ThrowsBadRequestAsync(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.ResetPassword)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = true;
|
||||
policy.SetDataModel(new ResetPasswordDataModel());
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(Task.FromResult(new Policy { Enabled = false }));
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Single Organization policy not enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_SingleOrg_AccountRecoveryEnabled_ThrowsBadRequest(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.SingleOrg)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = false;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.ResetPassword)
|
||||
.Returns(new Policy { Enabled = true });
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Account recovery policy is enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPoliciesApplicableToUserAsync_WithRequireSsoTypeFilter_WithDefaultOrganizationUserStatusFilter_ReturnsNoPolicies(Guid userId, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
@ -816,32 +143,4 @@ public class PolicyServiceTests
|
||||
new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.DisableSend, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.User, OrganizationUserStatus = OrganizationUserStatusType.Invited, IsProvider = true }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_GivenOrganizationUsingPoliciesAndHasVerifiedDomains_WhenSingleOrgPolicyIsDisabled_ThenAnErrorShouldBeThrownOrganizationHasVerifiedDomains(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.SingleOrg)] Policy policy, Organization org, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
org.Id = policy.OrganizationId;
|
||||
org.UsePolicies = true;
|
||||
|
||||
policy.Enabled = false;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(policy.OrganizationId)
|
||||
.Returns(org);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.HasVerifiedDomainsAsync(org.Id)
|
||||
.Returns(true);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy, null));
|
||||
|
||||
Assert.Equal("The Single organization policy is required for organizations that have enabled domain verification.", badRequestException.Message);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
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;
|
||||
@ -338,16 +339,26 @@ public class SsoConfigServiceTests
|
||||
|
||||
await sutProvider.Sut.SaveAsync(ssoConfig, organization);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyService>().Received(1)
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>().Received(1)
|
||||
.SaveAsync(
|
||||
Arg.Is<Policy>(t => t.Type == PolicyType.SingleOrg),
|
||||
null
|
||||
Arg.Is<PolicyUpdate>(t => t.Type == PolicyType.SingleOrg &&
|
||||
t.OrganizationId == organization.Id &&
|
||||
t.Enabled)
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyService>().Received(1)
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>().Received(1)
|
||||
.SaveAsync(
|
||||
Arg.Is<Policy>(t => t.Type == PolicyType.ResetPassword && t.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled),
|
||||
null
|
||||
Arg.Is<PolicyUpdate>(t => t.Type == PolicyType.ResetPassword &&
|
||||
t.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled &&
|
||||
t.OrganizationId == organization.Id &&
|
||||
t.Enabled)
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>().Received(1)
|
||||
.SaveAsync(
|
||||
Arg.Is<PolicyUpdate>(t => t.Type == PolicyType.RequireSso &&
|
||||
t.OrganizationId == organization.Id &&
|
||||
t.Enabled)
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<ISsoConfigRepository>().ReceivedWithAnyArgs()
|
||||
|
@ -1581,21 +1581,18 @@ public class SubscriberServiceTests
|
||||
|
||||
#region VerifyBankAccount
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task VerifyBankAccount_NullSubscriber_ThrowsArgumentNullException(
|
||||
SutProvider<SubscriberService> sutProvider) => await Assert.ThrowsAsync<ArgumentNullException>(
|
||||
() => sutProvider.Sut.VerifyBankAccount(null, (0, 0)));
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task VerifyBankAccount_NoSetupIntentId_ThrowsBillingException(
|
||||
Provider provider,
|
||||
SutProvider<SubscriberService> sutProvider) => await ThrowsBillingExceptionAsync(() => sutProvider.Sut.VerifyBankAccount(provider, (1, 1)));
|
||||
SutProvider<SubscriberService> sutProvider) => await ThrowsBillingExceptionAsync(() => sutProvider.Sut.VerifyBankAccount(provider, ""));
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task VerifyBankAccount_MakesCorrectInvocations(
|
||||
Provider provider,
|
||||
SutProvider<SubscriberService> sutProvider)
|
||||
{
|
||||
const string descriptorCode = "SM1234";
|
||||
|
||||
var setupIntent = new SetupIntent
|
||||
{
|
||||
Id = "setup_intent_id",
|
||||
@ -1608,11 +1605,11 @@ public class SubscriberServiceTests
|
||||
|
||||
stripeAdapter.SetupIntentGet(setupIntent.Id).Returns(setupIntent);
|
||||
|
||||
await sutProvider.Sut.VerifyBankAccount(provider, (1, 1));
|
||||
await sutProvider.Sut.VerifyBankAccount(provider, descriptorCode);
|
||||
|
||||
await stripeAdapter.Received(1).SetupIntentVerifyMicroDeposit(setupIntent.Id,
|
||||
Arg.Is<SetupIntentVerifyMicrodepositsOptions>(
|
||||
options => options.Amounts[0] == 1 && options.Amounts[1] == 1));
|
||||
options => options.DescriptorCode == descriptorCode));
|
||||
|
||||
await stripeAdapter.Received(1).PaymentMethodAttachAsync(setupIntent.PaymentMethodId,
|
||||
Arg.Is<PaymentMethodAttachOptions>(
|
||||
|
@ -0,0 +1,197 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.Commands;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.KeyManagement.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.KeyManagement.Commands;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class RegenerateUserAsymmetricKeysCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegenerateKeysAsync_NoCurrentContext_NotFoundException(
|
||||
SutProvider<RegenerateUserAsymmetricKeysCommand> sutProvider,
|
||||
UserAsymmetricKeys userAsymmetricKeys)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.ReturnsNullForAnyArgs();
|
||||
var usersOrganizationAccounts = new List<OrganizationUser>();
|
||||
var designatedEmergencyAccess = new List<EmergencyAccessDetails>();
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.RegenerateKeysAsync(userAsymmetricKeys,
|
||||
usersOrganizationAccounts, designatedEmergencyAccess));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegenerateKeysAsync_UserHasNoSharedAccess_Success(
|
||||
SutProvider<RegenerateUserAsymmetricKeysCommand> sutProvider,
|
||||
UserAsymmetricKeys userAsymmetricKeys)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.ReturnsForAnyArgs(userAsymmetricKeys.UserId);
|
||||
var usersOrganizationAccounts = new List<OrganizationUser>();
|
||||
var designatedEmergencyAccess = new List<EmergencyAccessDetails>();
|
||||
|
||||
await sutProvider.Sut.RegenerateKeysAsync(userAsymmetricKeys,
|
||||
usersOrganizationAccounts, designatedEmergencyAccess);
|
||||
|
||||
await sutProvider.GetDependency<IUserAsymmetricKeysRepository>()
|
||||
.Received(1)
|
||||
.RegenerateUserAsymmetricKeysAsync(Arg.Is(userAsymmetricKeys));
|
||||
await sutProvider.GetDependency<IPushNotificationService>()
|
||||
.Received(1)
|
||||
.PushSyncSettingsAsync(Arg.Is(userAsymmetricKeys.UserId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(false, false, true)]
|
||||
[BitAutoData(false, true, false)]
|
||||
[BitAutoData(false, true, true)]
|
||||
[BitAutoData(true, false, false)]
|
||||
[BitAutoData(true, false, true)]
|
||||
[BitAutoData(true, true, false)]
|
||||
[BitAutoData(true, true, true)]
|
||||
public async Task RegenerateKeysAsync_UserIdMisMatch_NotFoundException(
|
||||
bool userAsymmetricKeysMismatch,
|
||||
bool orgMismatch,
|
||||
bool emergencyAccessMismatch,
|
||||
SutProvider<RegenerateUserAsymmetricKeysCommand> sutProvider,
|
||||
UserAsymmetricKeys userAsymmetricKeys,
|
||||
ICollection<OrganizationUser> usersOrganizationAccounts,
|
||||
ICollection<EmergencyAccessDetails> designatedEmergencyAccess)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId
|
||||
.ReturnsForAnyArgs(userAsymmetricKeysMismatch ? new Guid() : userAsymmetricKeys.UserId);
|
||||
|
||||
if (!orgMismatch)
|
||||
{
|
||||
usersOrganizationAccounts =
|
||||
SetupOrganizationUserAccounts(userAsymmetricKeys.UserId, usersOrganizationAccounts);
|
||||
}
|
||||
|
||||
if (!emergencyAccessMismatch)
|
||||
{
|
||||
designatedEmergencyAccess = SetupEmergencyAccess(userAsymmetricKeys.UserId, designatedEmergencyAccess);
|
||||
}
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.RegenerateKeysAsync(userAsymmetricKeys,
|
||||
usersOrganizationAccounts, designatedEmergencyAccess));
|
||||
|
||||
await sutProvider.GetDependency<IUserAsymmetricKeysRepository>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.RegenerateUserAsymmetricKeysAsync(Arg.Any<UserAsymmetricKeys>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.PushSyncSettingsAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserStatusType.Confirmed)]
|
||||
[BitAutoData(OrganizationUserStatusType.Revoked)]
|
||||
public async Task RegenerateKeysAsync_UserInOrganizations_BadRequestException(
|
||||
OrganizationUserStatusType organizationUserStatus,
|
||||
SutProvider<RegenerateUserAsymmetricKeysCommand> sutProvider,
|
||||
UserAsymmetricKeys userAsymmetricKeys,
|
||||
ICollection<OrganizationUser> usersOrganizationAccounts)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.ReturnsForAnyArgs(userAsymmetricKeys.UserId);
|
||||
usersOrganizationAccounts = CreateInOrganizationAccounts(userAsymmetricKeys.UserId, organizationUserStatus,
|
||||
usersOrganizationAccounts);
|
||||
var designatedEmergencyAccess = new List<EmergencyAccessDetails>();
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.RegenerateKeysAsync(userAsymmetricKeys,
|
||||
usersOrganizationAccounts, designatedEmergencyAccess));
|
||||
|
||||
await sutProvider.GetDependency<IUserAsymmetricKeysRepository>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.RegenerateUserAsymmetricKeysAsync(Arg.Any<UserAsymmetricKeys>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.PushSyncSettingsAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(EmergencyAccessStatusType.Confirmed)]
|
||||
[BitAutoData(EmergencyAccessStatusType.RecoveryApproved)]
|
||||
[BitAutoData(EmergencyAccessStatusType.RecoveryInitiated)]
|
||||
public async Task RegenerateKeysAsync_UserHasDesignatedEmergencyAccess_BadRequestException(
|
||||
EmergencyAccessStatusType statusType,
|
||||
SutProvider<RegenerateUserAsymmetricKeysCommand> sutProvider,
|
||||
UserAsymmetricKeys userAsymmetricKeys,
|
||||
ICollection<EmergencyAccessDetails> designatedEmergencyAccess)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.ReturnsForAnyArgs(userAsymmetricKeys.UserId);
|
||||
designatedEmergencyAccess =
|
||||
CreateDesignatedEmergencyAccess(userAsymmetricKeys.UserId, statusType, designatedEmergencyAccess);
|
||||
var usersOrganizationAccounts = new List<OrganizationUser>();
|
||||
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.RegenerateKeysAsync(userAsymmetricKeys,
|
||||
usersOrganizationAccounts, designatedEmergencyAccess));
|
||||
|
||||
await sutProvider.GetDependency<IUserAsymmetricKeysRepository>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.RegenerateUserAsymmetricKeysAsync(Arg.Any<UserAsymmetricKeys>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.PushSyncSettingsAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
private static ICollection<OrganizationUser> CreateInOrganizationAccounts(Guid userId,
|
||||
OrganizationUserStatusType organizationUserStatus, ICollection<OrganizationUser> organizationUserAccounts)
|
||||
{
|
||||
foreach (var organizationUserAccount in organizationUserAccounts)
|
||||
{
|
||||
organizationUserAccount.UserId = userId;
|
||||
organizationUserAccount.Status = organizationUserStatus;
|
||||
}
|
||||
|
||||
return organizationUserAccounts;
|
||||
}
|
||||
|
||||
private static ICollection<EmergencyAccessDetails> CreateDesignatedEmergencyAccess(Guid userId,
|
||||
EmergencyAccessStatusType status, ICollection<EmergencyAccessDetails> designatedEmergencyAccess)
|
||||
{
|
||||
foreach (var designated in designatedEmergencyAccess)
|
||||
{
|
||||
designated.GranteeId = userId;
|
||||
designated.Status = status;
|
||||
}
|
||||
|
||||
return designatedEmergencyAccess;
|
||||
}
|
||||
|
||||
private static ICollection<OrganizationUser> SetupOrganizationUserAccounts(Guid userId,
|
||||
ICollection<OrganizationUser> organizationUserAccounts)
|
||||
{
|
||||
foreach (var organizationUserAccount in organizationUserAccounts)
|
||||
{
|
||||
organizationUserAccount.UserId = userId;
|
||||
}
|
||||
|
||||
return organizationUserAccounts;
|
||||
}
|
||||
|
||||
private static ICollection<EmergencyAccessDetails> SetupEmergencyAccess(Guid userId,
|
||||
ICollection<EmergencyAccessDetails> emergencyAccessDetails)
|
||||
{
|
||||
foreach (var emergencyAccessDetail in emergencyAccessDetails)
|
||||
{
|
||||
emergencyAccessDetail.GranteeId = userId;
|
||||
}
|
||||
|
||||
return emergencyAccessDetails;
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.UserFeatures.UserKey.Implementations;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.KeyManagement.UserKey.Implementations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Identity;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.UserFeatures.UserKey;
|
||||
namespace Bit.Core.Test.KeyManagement.UserFeatures.UserKey;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class RotateUserKeyCommandTests
|
@ -111,7 +111,8 @@ public static class OrganizationLicenseFileFixtures
|
||||
SmServiceAccounts = 8,
|
||||
MaxAutoscaleSmSeats = 101,
|
||||
MaxAutoscaleSmServiceAccounts = 102,
|
||||
LimitCollectionCreationDeletion = true,
|
||||
LimitCollectionCreation = true,
|
||||
LimitCollectionDeletion = true,
|
||||
AllowAdminAccessToAllCollectionItems = true,
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
@ -36,7 +37,7 @@ public class OrganizationLicenseTests
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationLicense.CurrentLicenseFileVersion)] // Previous version (this property is 1 behind)
|
||||
[BitAutoData(OrganizationLicense.CurrentLicenseFileVersion + 1)] // Current version
|
||||
public void OrganizationLicense_LoadedFromDisk_VerifyData_Passes(int licenseVersion)
|
||||
public void OrganizationLicense_LoadedFromDisk_VerifyData_Passes(int licenseVersion, ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var license = OrganizationLicenseFileFixtures.GetVersion(licenseVersion);
|
||||
|
||||
@ -49,7 +50,7 @@ public class OrganizationLicenseTests
|
||||
{
|
||||
Id = new Guid(OrganizationLicenseFileFixtures.InstallationId)
|
||||
});
|
||||
Assert.True(license.VerifyData(organization, globalSettings));
|
||||
Assert.True(license.VerifyData(organization, claimsPrincipal, globalSettings));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -9,9 +9,32 @@ public class NotificationStatusDetailsCustomization : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<NotificationStatusDetails>(composer => composer.With(n => n.Id, Guid.NewGuid())
|
||||
.With(n => n.UserId, Guid.NewGuid())
|
||||
.With(n => n.OrganizationId, Guid.NewGuid()));
|
||||
fixture.Customize<NotificationStatusDetails>(composer =>
|
||||
{
|
||||
return composer.With(n => n.Id, Guid.NewGuid())
|
||||
.With(n => n.UserId, Guid.NewGuid())
|
||||
.With(n => n.OrganizationId, Guid.NewGuid());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class NotificationStatusDetailsListCustomization(int count) : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
var customization = new NotificationStatusDetailsCustomization();
|
||||
fixture.Customize<IEnumerable<NotificationStatusDetails>>(composer => composer.FromFactory(() =>
|
||||
{
|
||||
var notifications = new List<NotificationStatusDetails>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
customization.Customize(fixture);
|
||||
var notificationStatusDetails = fixture.Create<NotificationStatusDetails>();
|
||||
notifications.Add(notificationStatusDetails);
|
||||
}
|
||||
|
||||
return notifications;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,3 +42,8 @@ public class NotificationStatusDetailsCustomizeAttribute : BitCustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization() => new NotificationStatusDetailsCustomization();
|
||||
}
|
||||
|
||||
public class NotificationStatusDetailsListCustomizeAttribute(int count) : BitCustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization() => new NotificationStatusDetailsListCustomization(count);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.NotificationCenter.Models.Data;
|
||||
using Bit.Core.NotificationCenter.Models.Filter;
|
||||
using Bit.Core.NotificationCenter.Queries;
|
||||
@ -19,37 +20,49 @@ namespace Bit.Core.Test.NotificationCenter.Queries;
|
||||
public class GetNotificationStatusDetailsForUserQueryTest
|
||||
{
|
||||
private static void Setup(SutProvider<GetNotificationStatusDetailsForUserQuery> sutProvider,
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter statusFilter, Guid? userId)
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter statusFilter, Guid? userId,
|
||||
PageOptions pageOptions, string? continuationToken)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<INotificationRepository>().GetByUserIdAndStatusAsync(
|
||||
userId.GetValueOrDefault(Guid.NewGuid()), Arg.Any<ClientType>(), statusFilter)
|
||||
.Returns(notificationsStatusDetails);
|
||||
sutProvider.GetDependency<INotificationRepository>()
|
||||
.GetByUserIdAndStatusAsync(userId.GetValueOrDefault(Guid.NewGuid()), Arg.Any<ClientType>(), statusFilter,
|
||||
pageOptions)
|
||||
.Returns(new PagedResult<NotificationStatusDetails>
|
||||
{
|
||||
Data = notificationsStatusDetails,
|
||||
ContinuationToken = continuationToken
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetByUserIdStatusFilterAsync_NotLoggedIn_NotFoundException(
|
||||
SutProvider<GetNotificationStatusDetailsForUserQuery> sutProvider,
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter)
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter,
|
||||
PageOptions pageOptions, string? continuationToken)
|
||||
{
|
||||
Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, userId: null);
|
||||
Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, userId: null, pageOptions,
|
||||
continuationToken);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter));
|
||||
sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter, pageOptions));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetByUserIdStatusFilterAsync_NotificationsFound_Returned(
|
||||
SutProvider<GetNotificationStatusDetailsForUserQuery> sutProvider,
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter)
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter,
|
||||
PageOptions pageOptions, string? continuationToken)
|
||||
{
|
||||
Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, Guid.NewGuid());
|
||||
Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, Guid.NewGuid(), pageOptions,
|
||||
continuationToken);
|
||||
|
||||
var actualNotificationsStatusDetails =
|
||||
await sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter);
|
||||
var actualNotificationsStatusDetailsPagedResult =
|
||||
await sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter, pageOptions);
|
||||
|
||||
Assert.Equal(notificationsStatusDetails, actualNotificationsStatusDetails);
|
||||
Assert.NotNull(actualNotificationsStatusDetailsPagedResult);
|
||||
Assert.Equal(notificationsStatusDetails, actualNotificationsStatusDetailsPagedResult.Data);
|
||||
Assert.Equal(continuationToken, actualNotificationsStatusDetailsPagedResult.ContinuationToken);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
@ -11,6 +13,7 @@ using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Stripe;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationLicenses;
|
||||
@ -62,4 +65,34 @@ public class CloudGetOrganizationLicenseQueryTests
|
||||
Assert.Equal(installationId, result.InstallationId);
|
||||
Assert.Equal(licenseSignature, result.SignatureBytes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetLicenseAsync_MSPManagedOrganization_UsesProviderSubscription(SutProvider<CloudGetOrganizationLicenseQuery> sutProvider,
|
||||
Organization organization, Guid installationId, Installation installation, SubscriptionInfo subInfo,
|
||||
byte[] licenseSignature, Provider provider)
|
||||
{
|
||||
organization.Status = OrganizationStatusType.Managed;
|
||||
organization.ExpirationDate = null;
|
||||
|
||||
subInfo.Subscription = new SubscriptionInfo.BillingSubscription(new Subscription
|
||||
{
|
||||
CurrentPeriodStart = DateTime.UtcNow,
|
||||
CurrentPeriodEnd = DateTime.UtcNow.AddMonths(1)
|
||||
});
|
||||
|
||||
installation.Enabled = true;
|
||||
sutProvider.GetDependency<IInstallationRepository>().GetByIdAsync(installationId).Returns(installation);
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organization.Id).Returns(provider);
|
||||
sutProvider.GetDependency<IPaymentService>().GetSubscriptionAsync(provider).Returns(subInfo);
|
||||
sutProvider.GetDependency<ILicensingService>().SignLicense(Arg.Any<ILicense>()).Returns(licenseSignature);
|
||||
|
||||
var result = await sutProvider.Sut.GetLicenseAsync(organization, installationId);
|
||||
|
||||
Assert.Equal(LicenseType.Organization, result.LicenseType);
|
||||
Assert.Equal(organization.Id, result.Id);
|
||||
Assert.Equal(installationId, result.InstallationId);
|
||||
Assert.Equal(licenseSignature, result.SignatureBytes);
|
||||
Assert.Equal(DateTime.UtcNow.AddYears(1).Date, result.Expires!.Value.Date);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
@ -48,6 +49,9 @@ public class UpdateOrganizationLicenseCommandTests
|
||||
license.InstallationId = globalSettings.Installation.Id;
|
||||
license.LicenseType = LicenseType.Organization;
|
||||
sutProvider.GetDependency<ILicensingService>().VerifyLicense(license).Returns(true);
|
||||
sutProvider.GetDependency<ILicensingService>()
|
||||
.GetClaimsPrincipalFromLicense(license)
|
||||
.Returns((ClaimsPrincipal)null);
|
||||
|
||||
// Passing values for SelfHostedOrganizationDetails.CanUseLicense
|
||||
// NSubstitute cannot override non-virtual members so we have to ensure the real method passes
|
||||
@ -80,7 +84,9 @@ public class UpdateOrganizationLicenseCommandTests
|
||||
.ReplaceAndUpdateCacheAsync(Arg.Is<Organization>(
|
||||
org => AssertPropertyEqual(license, org,
|
||||
"Id", "MaxStorageGb", "Issued", "Refresh", "Version", "Trial", "LicenseType",
|
||||
"Hash", "Signature", "SignatureBytes", "InstallationId", "Expires", "ExpirationWithoutGracePeriod") &&
|
||||
"Hash", "Signature", "SignatureBytes", "InstallationId", "Expires",
|
||||
"ExpirationWithoutGracePeriod", "Token", "LimitCollectionCreationDeletion",
|
||||
"LimitCollectionCreation", "LimitCollectionDeletion", "AllowAdminAccessToAllCollectionItems") &&
|
||||
// Same property but different name, use explicit mapping
|
||||
org.ExpirationDate == license.Expires));
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
using System.Text.Json;
|
||||
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.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Auth.Enums;
|
||||
@ -9,13 +12,17 @@ using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Billing.Services;
|
||||
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.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
@ -62,6 +69,9 @@ public class UserServiceTests
|
||||
sutProvider.GetDependency<ILicensingService>()
|
||||
.VerifyLicense(userLicense)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<ILicensingService>()
|
||||
.GetClaimsPrincipalFromLicense(userLicense)
|
||||
.Returns((ClaimsPrincipal)null);
|
||||
|
||||
await sutProvider.Sut.UpdateLicenseAsync(user, userLicense);
|
||||
|
||||
@ -231,41 +241,7 @@ public class UserServiceTests
|
||||
});
|
||||
|
||||
// HACK: SutProvider is being weird about not injecting the IPasswordHasher that I configured
|
||||
var sut = new UserService(
|
||||
sutProvider.GetDependency<IUserRepository>(),
|
||||
sutProvider.GetDependency<ICipherRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationRepository>(),
|
||||
sutProvider.GetDependency<IMailService>(),
|
||||
sutProvider.GetDependency<IPushNotificationService>(),
|
||||
sutProvider.GetDependency<IUserStore<User>>(),
|
||||
sutProvider.GetDependency<IOptions<IdentityOptions>>(),
|
||||
sutProvider.GetDependency<IPasswordHasher<User>>(),
|
||||
sutProvider.GetDependency<IEnumerable<IUserValidator<User>>>(),
|
||||
sutProvider.GetDependency<IEnumerable<IPasswordValidator<User>>>(),
|
||||
sutProvider.GetDependency<ILookupNormalizer>(),
|
||||
sutProvider.GetDependency<IdentityErrorDescriber>(),
|
||||
sutProvider.GetDependency<IServiceProvider>(),
|
||||
sutProvider.GetDependency<ILogger<UserManager<User>>>(),
|
||||
sutProvider.GetDependency<ILicensingService>(),
|
||||
sutProvider.GetDependency<IEventService>(),
|
||||
sutProvider.GetDependency<IApplicationCacheService>(),
|
||||
sutProvider.GetDependency<IDataProtectionProvider>(),
|
||||
sutProvider.GetDependency<IPaymentService>(),
|
||||
sutProvider.GetDependency<IPolicyRepository>(),
|
||||
sutProvider.GetDependency<IPolicyService>(),
|
||||
sutProvider.GetDependency<IReferenceEventService>(),
|
||||
sutProvider.GetDependency<IFido2>(),
|
||||
sutProvider.GetDependency<ICurrentContext>(),
|
||||
sutProvider.GetDependency<IGlobalSettings>(),
|
||||
sutProvider.GetDependency<IAcceptOrgUserCommand>(),
|
||||
sutProvider.GetDependency<IProviderUserRepository>(),
|
||||
sutProvider.GetDependency<IStripeSyncService>(),
|
||||
new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>(),
|
||||
sutProvider.GetDependency<IFeatureService>(),
|
||||
sutProvider.GetDependency<IPremiumUserBillingService>(),
|
||||
sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
);
|
||||
var sut = RebuildSut(sutProvider);
|
||||
|
||||
var actualIsVerified = await sut.VerifySecretAsync(user, secret);
|
||||
|
||||
@ -349,6 +325,262 @@ public class UserServiceTests
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DisableTwoFactorProviderAsync_WhenOrganizationHas2FAPolicyEnabled_DisablingAllProviders_RemovesUserFromOrganizationAndSendsEmail(
|
||||
SutProvider<UserService> sutProvider, User user, Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||
{
|
||||
[TwoFactorProviderType.Email] = new() { Enabled = true }
|
||||
});
|
||||
sutProvider.GetDependency<IPolicyService>()
|
||||
.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication)
|
||||
.Returns(
|
||||
[
|
||||
new OrganizationUserPolicyDetails
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||
PolicyEnabled = true
|
||||
}
|
||||
]);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary<TwoFactorProviderType, TwoFactorProvider>(), JsonHelpers.LegacyEnumKeyResolver);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DisableTwoFactorProviderAsync(user, TwoFactorProviderType.Email);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IUserRepository>()
|
||||
.Received(1)
|
||||
.ReplaceAsync(Arg.Is<User>(u => u.Id == user.Id && u.TwoFactorProviders == expectedSavedProviders));
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogUserEventAsync(user.Id, EventType.User_Disabled2fa);
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RemoveUserAsync(organization.Id, user.Id);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), user.Email);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DisableTwoFactorProviderAsync_WhenOrganizationHas2FAPolicyEnabled_UserHasOneProviderEnabled_DoesNotRemoveUserFromOrganization(
|
||||
SutProvider<UserService> sutProvider, User user, Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||
{
|
||||
[TwoFactorProviderType.Email] = new() { Enabled = true },
|
||||
[TwoFactorProviderType.Remember] = new() { Enabled = true }
|
||||
});
|
||||
sutProvider.GetDependency<IPolicyService>()
|
||||
.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication)
|
||||
.Returns(
|
||||
[
|
||||
new OrganizationUserPolicyDetails
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||
PolicyEnabled = true
|
||||
}
|
||||
]);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||
{
|
||||
[TwoFactorProviderType.Remember] = new() { Enabled = true }
|
||||
}, JsonHelpers.LegacyEnumKeyResolver);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DisableTwoFactorProviderAsync(user, TwoFactorProviderType.Email);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IUserRepository>()
|
||||
.Received(1)
|
||||
.ReplaceAsync(Arg.Is<User>(u => u.Id == user.Id && u.TwoFactorProviders == expectedSavedProviders));
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogUserEventAsync(user.Id, EventType.User_Disabled2fa);
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.RemoveUserAsync(default, default);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DisableTwoFactorProviderAsync_WithAccountDeprovisioningEnabled_WhenOrganizationHas2FAPolicyEnabled_WhenUserIsManaged_DisablingAllProviders_RemovesOrRevokesUserAndSendsEmail(
|
||||
SutProvider<UserService> sutProvider, User user, Organization organization1, Organization organization2)
|
||||
{
|
||||
// Arrange
|
||||
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||
{
|
||||
[TwoFactorProviderType.Email] = new() { Enabled = true }
|
||||
});
|
||||
organization1.Enabled = organization2.Enabled = true;
|
||||
organization1.UseSso = organization2.UseSso = true;
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IPolicyService>()
|
||||
.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication)
|
||||
.Returns(
|
||||
[
|
||||
new OrganizationUserPolicyDetails
|
||||
{
|
||||
OrganizationId = organization1.Id,
|
||||
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||
PolicyEnabled = true
|
||||
},
|
||||
new OrganizationUserPolicyDetails
|
||||
{
|
||||
OrganizationId = organization2.Id,
|
||||
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||
PolicyEnabled = true
|
||||
}
|
||||
]);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization1.Id)
|
||||
.Returns(organization1);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization2.Id)
|
||||
.Returns(organization2);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByVerifiedUserEmailDomainAsync(user.Id)
|
||||
.Returns(new[] { organization1 });
|
||||
var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary<TwoFactorProviderType, TwoFactorProvider>(), JsonHelpers.LegacyEnumKeyResolver);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DisableTwoFactorProviderAsync(user, TwoFactorProviderType.Email);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IUserRepository>()
|
||||
.Received(1)
|
||||
.ReplaceAsync(Arg.Is<User>(u => u.Id == user.Id && u.TwoFactorProviders == expectedSavedProviders));
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogUserEventAsync(user.Id, EventType.User_Disabled2fa);
|
||||
|
||||
// Revoke the user from the first organization because they are managed by it
|
||||
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RevokeNonCompliantOrganizationUsersAsync(
|
||||
Arg.Is<RevokeOrganizationUsersRequest>(r => r.OrganizationId == organization1.Id &&
|
||||
r.OrganizationUsers.First().UserId == user.Id &&
|
||||
r.OrganizationUsers.First().OrganizationId == organization1.Id));
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRevokedForTwoFactoryPolicyEmailAsync(organization1.DisplayName(), user.Email);
|
||||
|
||||
// Remove the user from the second organization because they are not managed by it
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RemoveUserAsync(organization2.Id, user.Id);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization2.DisplayName(), user.Email);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ResendNewDeviceVerificationEmail_UserNull_SendOTPAsyncNotCalled(
|
||||
SutProvider<UserService> sutProvider, string email, string secret)
|
||||
{
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.Returns(null as User);
|
||||
|
||||
await sutProvider.Sut.ResendNewDeviceVerificationEmail(email, secret);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOTPEmailAsync(Arg.Any<string>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ResendNewDeviceVerificationEmail_SecretNotValid_SendOTPAsyncNotCalled(
|
||||
SutProvider<UserService> sutProvider, string email, string secret)
|
||||
{
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.Returns(null as User);
|
||||
|
||||
await sutProvider.Sut.ResendNewDeviceVerificationEmail(email, secret);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOTPEmailAsync(Arg.Any<string>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ResendNewDeviceVerificationEmail_SendsToken_Success(
|
||||
SutProvider<UserService> sutProvider, User user)
|
||||
{
|
||||
// Arrange
|
||||
var testPassword = "test_password";
|
||||
var tokenProvider = SetupFakeTokenProvider(sutProvider, user);
|
||||
SetupUserAndDevice(user, true);
|
||||
|
||||
// Setup the fake password verification
|
||||
var substitutedUserPasswordStore = Substitute.For<IUserPasswordStore<User>>();
|
||||
substitutedUserPasswordStore
|
||||
.GetPasswordHashAsync(user, Arg.Any<CancellationToken>())
|
||||
.Returns((ci) =>
|
||||
{
|
||||
return Task.FromResult("hashed_test_password");
|
||||
});
|
||||
|
||||
sutProvider.SetDependency<IUserStore<User>>(substitutedUserPasswordStore, "store");
|
||||
|
||||
sutProvider.GetDependency<IPasswordHasher<User>>("passwordHasher")
|
||||
.VerifyHashedPassword(user, "hashed_test_password", testPassword)
|
||||
.Returns((ci) =>
|
||||
{
|
||||
return PasswordVerificationResult.Success;
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(user.Email)
|
||||
.Returns(user);
|
||||
|
||||
// HACK: SutProvider is being weird about not injecting the IPasswordHasher that I configured
|
||||
var sut = RebuildSut(sutProvider);
|
||||
|
||||
await sut.ResendNewDeviceVerificationEmail(user.Email, testPassword);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOTPEmailAsync(user.Email, Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData("")]
|
||||
[BitAutoData("null")]
|
||||
public async Task SendOTPAsync_UserEmailNull_ThrowsBadRequest(
|
||||
string email,
|
||||
SutProvider<UserService> 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<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOTPEmailAsync(Arg.Any<string>(), Arg.Any<string>());
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetupUserAndDevice(User user,
|
||||
bool shouldHavePassword)
|
||||
{
|
||||
@ -400,4 +632,44 @@ public class UserServiceTests
|
||||
|
||||
return fakeUserTwoFactorProvider;
|
||||
}
|
||||
|
||||
private IUserService RebuildSut(SutProvider<UserService> sutProvider)
|
||||
{
|
||||
return new UserService(
|
||||
sutProvider.GetDependency<IUserRepository>(),
|
||||
sutProvider.GetDependency<ICipherRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationRepository>(),
|
||||
sutProvider.GetDependency<IMailService>(),
|
||||
sutProvider.GetDependency<IPushNotificationService>(),
|
||||
sutProvider.GetDependency<IUserStore<User>>(),
|
||||
sutProvider.GetDependency<IOptions<IdentityOptions>>(),
|
||||
sutProvider.GetDependency<IPasswordHasher<User>>(),
|
||||
sutProvider.GetDependency<IEnumerable<IUserValidator<User>>>(),
|
||||
sutProvider.GetDependency<IEnumerable<IPasswordValidator<User>>>(),
|
||||
sutProvider.GetDependency<ILookupNormalizer>(),
|
||||
sutProvider.GetDependency<IdentityErrorDescriber>(),
|
||||
sutProvider.GetDependency<IServiceProvider>(),
|
||||
sutProvider.GetDependency<ILogger<UserManager<User>>>(),
|
||||
sutProvider.GetDependency<ILicensingService>(),
|
||||
sutProvider.GetDependency<IEventService>(),
|
||||
sutProvider.GetDependency<IApplicationCacheService>(),
|
||||
sutProvider.GetDependency<IDataProtectionProvider>(),
|
||||
sutProvider.GetDependency<IPaymentService>(),
|
||||
sutProvider.GetDependency<IPolicyRepository>(),
|
||||
sutProvider.GetDependency<IPolicyService>(),
|
||||
sutProvider.GetDependency<IReferenceEventService>(),
|
||||
sutProvider.GetDependency<IFido2>(),
|
||||
sutProvider.GetDependency<ICurrentContext>(),
|
||||
sutProvider.GetDependency<IGlobalSettings>(),
|
||||
sutProvider.GetDependency<IAcceptOrgUserCommand>(),
|
||||
sutProvider.GetDependency<IProviderUserRepository>(),
|
||||
sutProvider.GetDependency<IStripeSyncService>(),
|
||||
new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>(),
|
||||
sutProvider.GetDependency<IFeatureService>(),
|
||||
sutProvider.GetDependency<IPremiumUserBillingService>(),
|
||||
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.ReportFeatures;
|
||||
using Bit.Core.Tools.ReportFeatures.Requests;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Core.Tools.Requests;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
|
@ -0,0 +1,104 @@
|
||||
using AutoFixture;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.ReportFeatures;
|
||||
using Bit.Core.Tools.ReportFeatures.Requests;
|
||||
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 DeletePasswordHealthReportApplicationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task DropPasswordHealthReportApplicationAsync_withValidRequest_Success(
|
||||
SutProvider<DropPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var passwordHealthReportApplications = fixture.CreateMany<PasswordHealthReportApplication>(2).ToList();
|
||||
// only take one id from the list - we only want to drop one record
|
||||
var request = fixture.Build<DropPasswordHealthReportApplicationRequest>()
|
||||
.With(x => x.PasswordHealthReportApplicationIds,
|
||||
passwordHealthReportApplications.Select(x => x.Id).Take(1).ToList())
|
||||
.Create();
|
||||
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.GetByOrganizationIdAsync(Arg.Any<Guid>())
|
||||
.Returns(passwordHealthReportApplications);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DropPasswordHealthReportApplicationAsync(request);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.Received(1)
|
||||
.GetByOrganizationIdAsync(request.OrganizationId);
|
||||
|
||||
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.Received(1)
|
||||
.DeleteAsync(Arg.Is<PasswordHealthReportApplication>(_ =>
|
||||
request.PasswordHealthReportApplicationIds.Contains(_.Id)));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DropPasswordHealthReportApplicationAsync_withValidRequest_nothingToDrop(
|
||||
SutProvider<DropPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var passwordHealthReportApplications = fixture.CreateMany<PasswordHealthReportApplication>(2).ToList();
|
||||
// we are passing invalid data
|
||||
var request = fixture.Build<DropPasswordHealthReportApplicationRequest>()
|
||||
.With(x => x.PasswordHealthReportApplicationIds, new List<Guid> { Guid.NewGuid() })
|
||||
.Create();
|
||||
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.GetByOrganizationIdAsync(Arg.Any<Guid>())
|
||||
.Returns(passwordHealthReportApplications);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DropPasswordHealthReportApplicationAsync(request);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.Received(1)
|
||||
.GetByOrganizationIdAsync(request.OrganizationId);
|
||||
|
||||
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.Received(0)
|
||||
.DeleteAsync(Arg.Any<PasswordHealthReportApplication>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DropPasswordHealthReportApplicationAsync_withNodata_fails(
|
||||
SutProvider<DropPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
// we are passing invalid data
|
||||
var request = fixture.Build<DropPasswordHealthReportApplicationRequest>()
|
||||
.Create();
|
||||
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.GetByOrganizationIdAsync(Arg.Any<Guid>())
|
||||
.Returns(null as List<PasswordHealthReportApplication>);
|
||||
|
||||
// Act
|
||||
await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.DropPasswordHealthReportApplicationAsync(request));
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.Received(1)
|
||||
.GetByOrganizationIdAsync(request.OrganizationId);
|
||||
|
||||
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.Received(0)
|
||||
.DeleteAsync(Arg.Any<PasswordHealthReportApplication>());
|
||||
}
|
||||
}
|
25
test/Core.Test/Vault/AutoFixture/SecurityTaskFixtures.cs
Normal file
25
test/Core.Test/Vault/AutoFixture/SecurityTaskFixtures.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using AutoFixture;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
||||
namespace Bit.Core.Test.Vault.AutoFixture;
|
||||
|
||||
public class SecurityTaskFixtures : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<SecurityTask>(composer =>
|
||||
composer
|
||||
.With(task => task.Id, Guid.NewGuid())
|
||||
.With(task => task.OrganizationId, Guid.NewGuid())
|
||||
.With(task => task.Status, SecurityTaskStatus.Pending)
|
||||
.Without(x => x.CipherId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class SecurityTaskCustomizeAttribute : BitCustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization() => new SecurityTaskFixtures();
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
#nullable enable
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Test.Vault.AutoFixture;
|
||||
using Bit.Core.Vault.Authorization;
|
||||
using Bit.Core.Vault.Commands;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Vault.Commands;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[SecurityTaskCustomize]
|
||||
public class MarkTaskAsCompletedCommandTest
|
||||
{
|
||||
private static void Setup(SutProvider<MarkTaskAsCompletedCommand> sutProvider, Guid taskId, SecurityTask? securityTask, Guid? userId, bool authorizedUpdate = false)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<ISecurityTaskRepository>()
|
||||
.GetByIdAsync(taskId)
|
||||
.Returns(securityTask);
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), securityTask ?? Arg.Any<SecurityTask>(),
|
||||
Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs =>
|
||||
reqs.Contains(SecurityTaskOperations.Update)))
|
||||
.Returns(authorizedUpdate ? AuthorizationResult.Success() : AuthorizationResult.Failed());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CompleteAsync_NotLoggedIn_NotFoundException(
|
||||
SutProvider<MarkTaskAsCompletedCommand> sutProvider,
|
||||
Guid taskId,
|
||||
SecurityTask securityTask)
|
||||
{
|
||||
Setup(sutProvider, taskId, securityTask, null, true);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CompleteAsync(taskId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CompleteAsync_TaskNotFound_NotFoundException(
|
||||
SutProvider<MarkTaskAsCompletedCommand> sutProvider,
|
||||
Guid taskId)
|
||||
{
|
||||
Setup(sutProvider, taskId, null, Guid.NewGuid(), true);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CompleteAsync(taskId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CompleteAsync_AuthorizationFailed_NotFoundException(
|
||||
SutProvider<MarkTaskAsCompletedCommand> sutProvider,
|
||||
Guid taskId,
|
||||
SecurityTask securityTask)
|
||||
{
|
||||
Setup(sutProvider, taskId, securityTask, Guid.NewGuid());
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CompleteAsync(taskId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CompleteAsync_Success(
|
||||
SutProvider<MarkTaskAsCompletedCommand> sutProvider,
|
||||
Guid taskId,
|
||||
SecurityTask securityTask)
|
||||
{
|
||||
Setup(sutProvider, taskId, securityTask, Guid.NewGuid(), true);
|
||||
|
||||
await sutProvider.Sut.CompleteAsync(taskId);
|
||||
|
||||
await sutProvider.GetDependency<ISecurityTaskRepository>().Received(1).ReplaceAsync(securityTask);
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
using AutoFixture;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Vault.Models.Data;
|
||||
using Bit.Core.Vault.Queries;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Vault.Queries;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationCiphersQueryTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetOrganizationCiphersInCollections_ReturnsFilteredCiphers(
|
||||
Guid organizationId, SutProvider<OrganizationCiphersQuery> sutProvider)
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
|
||||
var otherCollectionId = Guid.NewGuid();
|
||||
var targetCollectionId = Guid.NewGuid();
|
||||
|
||||
var otherCipher = fixture.Create<CipherOrganizationDetails>();
|
||||
var targetCipher = fixture.Create<CipherOrganizationDetails>();
|
||||
var bothCipher = fixture.Create<CipherOrganizationDetails>();
|
||||
var noCipher = fixture.Create<CipherOrganizationDetails>();
|
||||
|
||||
var ciphers = new List<CipherOrganizationDetails>
|
||||
{
|
||||
otherCipher, // not in the target collection
|
||||
targetCipher, // in the target collection
|
||||
bothCipher, // in both collections
|
||||
noCipher // not in any collection
|
||||
};
|
||||
ciphers.ForEach(c =>
|
||||
{
|
||||
c.OrganizationId = organizationId;
|
||||
c.UserId = null;
|
||||
});
|
||||
|
||||
var otherCollectionCipher = new CollectionCipher
|
||||
{
|
||||
CollectionId = otherCollectionId,
|
||||
CipherId = otherCipher.Id
|
||||
};
|
||||
var targetCollectionCipher = new CollectionCipher
|
||||
{
|
||||
CollectionId = targetCollectionId,
|
||||
CipherId = targetCipher.Id
|
||||
};
|
||||
var bothCollectionCipher1 = new CollectionCipher
|
||||
{
|
||||
CollectionId = targetCollectionId,
|
||||
CipherId = bothCipher.Id
|
||||
};
|
||||
var bothCollectionCipher2 = new CollectionCipher
|
||||
{
|
||||
CollectionId = otherCollectionId,
|
||||
CipherId = bothCipher.Id
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ICipherRepository>().GetManyOrganizationDetailsByOrganizationIdAsync(organizationId)
|
||||
.Returns(ciphers);
|
||||
|
||||
sutProvider.GetDependency<ICollectionCipherRepository>().GetManyByOrganizationIdAsync(organizationId).Returns(
|
||||
[
|
||||
targetCollectionCipher,
|
||||
otherCollectionCipher,
|
||||
bothCollectionCipher1,
|
||||
bothCollectionCipher2
|
||||
]);
|
||||
|
||||
var result = await sutProvider
|
||||
.Sut
|
||||
.GetOrganizationCiphersByCollectionIds(organizationId, [targetCollectionId]);
|
||||
result = result.ToList();
|
||||
|
||||
Assert.Equal(2, result.Count());
|
||||
Assert.Contains(result, c =>
|
||||
c.Id == targetCipher.Id &&
|
||||
c.CollectionIds.Count() == 1 &&
|
||||
c.CollectionIds.Any(cId => cId == targetCollectionId));
|
||||
Assert.Contains(result, c =>
|
||||
c.Id == bothCipher.Id &&
|
||||
c.CollectionIds.Count() == 2 &&
|
||||
c.CollectionIds.Any(cId => cId == targetCollectionId) &&
|
||||
c.CollectionIds.Any(cId => cId == otherCollectionId));
|
||||
}
|
||||
}
|
@ -5,14 +5,11 @@ using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Identity.IdentityServer.RequestValidators;
|
||||
using Bit.Identity.Models.Request.Accounts;
|
||||
using Bit.IntegrationTestCommon.Factories;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Duende.IdentityServer.Validation;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Identity.IntegrationTest.RequestValidation;
|
||||
@ -217,48 +214,6 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
|
||||
Assert.Equal("Username or password is incorrect. Try again.", errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAsync_DeviceSaveAsync_ReturnsNullDevice_ErrorResult()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new IdentityApplicationFactory();
|
||||
|
||||
// Stub DeviceValidator
|
||||
factory.SubstituteService<IDeviceValidator>(sub =>
|
||||
{
|
||||
sub.SaveDeviceAsync(Arg.Any<User>(), Arg.Any<ValidatedTokenRequest>())
|
||||
.Returns(null as Device);
|
||||
});
|
||||
|
||||
// Add User
|
||||
await factory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = DefaultUsername,
|
||||
MasterPasswordHash = DefaultPassword
|
||||
});
|
||||
var userManager = factory.GetService<UserManager<User>>();
|
||||
await factory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = DefaultUsername,
|
||||
MasterPasswordHash = DefaultPassword
|
||||
});
|
||||
var user = await userManager.FindByEmailAsync(DefaultUsername);
|
||||
Assert.NotNull(user);
|
||||
|
||||
// Act
|
||||
var context = await factory.Server.PostAsync("/connect/token",
|
||||
GetFormUrlEncodedContent(),
|
||||
context => context.SetAuthEmail(DefaultUsername));
|
||||
|
||||
// Assert
|
||||
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
|
||||
var root = body.RootElement;
|
||||
|
||||
var errorModel = AssertHelper.AssertJsonProperty(root, "ErrorModel", JsonValueKind.Object);
|
||||
var errorMessage = AssertHelper.AssertJsonProperty(errorModel, "Message", JsonValueKind.String).GetString();
|
||||
Assert.Equal("No device information provided.", errorMessage);
|
||||
}
|
||||
|
||||
private async Task EnsureUserCreatedAsync(IdentityApplicationFactory factory = null)
|
||||
{
|
||||
factory ??= _factory;
|
||||
@ -290,6 +245,18 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
|
||||
});
|
||||
}
|
||||
|
||||
private FormUrlEncodedContent GetDefaultFormUrlEncodedContentWithoutDevice()
|
||||
{
|
||||
return new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "scope", "api offline_access" },
|
||||
{ "client_id", "web" },
|
||||
{ "grant_type", "password" },
|
||||
{ "username", DefaultUsername },
|
||||
{ "password", DefaultPassword },
|
||||
});
|
||||
}
|
||||
|
||||
private static string DeviceTypeAsString(DeviceType deviceType)
|
||||
{
|
||||
return ((int)deviceType).ToString();
|
||||
|
@ -22,7 +22,6 @@ using NSubstitute;
|
||||
using Xunit;
|
||||
using AuthFixtures = Bit.Identity.Test.AutoFixture;
|
||||
|
||||
|
||||
namespace Bit.Identity.Test.IdentityServer;
|
||||
|
||||
public class BaseRequestValidatorTests
|
||||
@ -82,10 +81,10 @@ public class BaseRequestValidatorTests
|
||||
}
|
||||
|
||||
/* Logic path
|
||||
ValidateAsync -> _Logger.LogInformation
|
||||
|-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
||||
|-> SetErrorResult
|
||||
*/
|
||||
* ValidateAsync -> _Logger.LogInformation
|
||||
* |-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
||||
* |-> SetErrorResult
|
||||
*/
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_IsBot_UserNotNull_ShouldBuildErrorResult_ShouldLogFailedLoginEvent(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||
@ -112,11 +111,11 @@ public class BaseRequestValidatorTests
|
||||
}
|
||||
|
||||
/* Logic path
|
||||
ValidateAsync -> UpdateFailedAuthDetailsAsync -> _mailService.SendFailedLoginAttemptsEmailAsync
|
||||
|-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
||||
(self hosted) |-> _logger.LogWarning()
|
||||
|-> SetErrorResult
|
||||
*/
|
||||
* ValidateAsync -> UpdateFailedAuthDetailsAsync -> _mailService.SendFailedLoginAttemptsEmailAsync
|
||||
* |-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
||||
* (self hosted) |-> _logger.LogWarning()
|
||||
* |-> SetErrorResult
|
||||
*/
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_ContextNotValid_SelfHosted_ShouldBuildErrorResult_ShouldLogWarning(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||
@ -140,10 +139,10 @@ public class BaseRequestValidatorTests
|
||||
}
|
||||
|
||||
/* Logic path
|
||||
ValidateAsync -> UpdateFailedAuthDetailsAsync -> _mailService.SendFailedLoginAttemptsEmailAsync
|
||||
|-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
||||
|-> SetErrorResult
|
||||
*/
|
||||
* ValidateAsync -> UpdateFailedAuthDetailsAsync -> _mailService.SendFailedLoginAttemptsEmailAsync
|
||||
* |-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
||||
* |-> SetErrorResult
|
||||
*/
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_ContextNotValid_MaxAttemptLogin_ShouldSendEmail(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||
@ -177,134 +176,97 @@ public class BaseRequestValidatorTests
|
||||
Assert.Equal("Username or password is incorrect. Try again.", errorResponse.Message);
|
||||
}
|
||||
|
||||
|
||||
/* Logic path
|
||||
ValidateAsync -> IsValidAuthTypeAsync -> SaveDeviceAsync -> BuildErrorResult
|
||||
*/
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_AuthCodeGrantType_DeviceNull_ShouldError(
|
||||
public async Task ValidateAsync_DeviceNotValidated_ShouldLogError(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||
CustomValidatorRequestContext requestContext,
|
||||
GrantValidationResult grantResult)
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||
_twoFactorAuthenticationValidator
|
||||
.RequiresTwoFactorAsync(Arg.Any<User>(), Arg.Any<ValidatedTokenRequest>())
|
||||
.Returns(Task.FromResult(new Tuple<bool, Organization>(false, default)));
|
||||
|
||||
// 1 -> to pass
|
||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||
_sut.isValid = true;
|
||||
|
||||
context.ValidatedTokenRequest.GrantType = "authorization_code";
|
||||
// 2 -> will result to false with no extra configuration
|
||||
// 3 -> set two factor to be false
|
||||
_twoFactorAuthenticationValidator
|
||||
.RequiresTwoFactorAsync(Arg.Any<User>(), tokenRequest)
|
||||
.Returns(Task.FromResult(new Tuple<bool, Organization>(false, null)));
|
||||
|
||||
// 4 -> set up device validator to fail
|
||||
requestContext.KnownDevice = false;
|
||||
tokenRequest.GrantType = "password";
|
||||
_deviceValidator.ValidateRequestDeviceAsync(Arg.Any<ValidatedTokenRequest>(), Arg.Any<CustomValidatorRequestContext>())
|
||||
.Returns(Task.FromResult(false));
|
||||
|
||||
// 5 -> not legacy user
|
||||
_userService.IsLegacyUser(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
await _sut.ValidateAsync(context);
|
||||
|
||||
var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
|
||||
|
||||
// Assert
|
||||
Assert.True(context.GrantResult.IsError);
|
||||
Assert.Equal("No device information provided.", errorResponse.Message);
|
||||
await _eventService.Received(1)
|
||||
.LogUserEventAsync(context.CustomValidatorRequestContext.User.Id, EventType.User_FailedLogIn);
|
||||
}
|
||||
|
||||
/* Logic path
|
||||
ValidateAsync -> IsValidAuthTypeAsync -> SaveDeviceAsync -> BuildSuccessResultAsync
|
||||
*/
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_ClientCredentialsGrantType_ShouldSucceed(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||
CustomValidatorRequestContext requestContext,
|
||||
GrantValidationResult grantResult,
|
||||
Device device)
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||
_twoFactorAuthenticationValidator
|
||||
.RequiresTwoFactorAsync(Arg.Any<User>(), Arg.Any<ValidatedTokenRequest>())
|
||||
.Returns(Task.FromResult(new Tuple<bool, Organization>(false, null)));
|
||||
|
||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||
_sut.isValid = true;
|
||||
|
||||
context.CustomValidatorRequestContext.User.CreationDate = DateTime.UtcNow - TimeSpan.FromDays(1);
|
||||
_globalSettings.DisableEmailNewDevice = false;
|
||||
|
||||
context.ValidatedTokenRequest.GrantType = "client_credentials"; // This || AuthCode will allow process to continue to get device
|
||||
|
||||
_deviceValidator.SaveDeviceAsync(Arg.Any<User>(), Arg.Any<ValidatedTokenRequest>())
|
||||
.Returns(device);
|
||||
// Act
|
||||
await _sut.ValidateAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.False(context.GrantResult.IsError);
|
||||
}
|
||||
|
||||
/* Logic path
|
||||
ValidateAsync -> IsValidAuthTypeAsync -> SaveDeviceAsync -> BuildSuccessResultAsync
|
||||
*/
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_ClientCredentialsGrantType_ExistingDevice_ShouldSucceed(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||
CustomValidatorRequestContext requestContext,
|
||||
GrantValidationResult grantResult,
|
||||
Device device)
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||
|
||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||
_sut.isValid = true;
|
||||
|
||||
context.CustomValidatorRequestContext.User.CreationDate = DateTime.UtcNow - TimeSpan.FromDays(1);
|
||||
_globalSettings.DisableEmailNewDevice = false;
|
||||
|
||||
context.ValidatedTokenRequest.GrantType = "client_credentials"; // This || AuthCode will allow process to continue to get device
|
||||
|
||||
_deviceValidator.SaveDeviceAsync(Arg.Any<User>(), Arg.Any<ValidatedTokenRequest>())
|
||||
.Returns(device);
|
||||
_twoFactorAuthenticationValidator
|
||||
.RequiresTwoFactorAsync(Arg.Any<User>(), Arg.Any<ValidatedTokenRequest>())
|
||||
.Returns(Task.FromResult(new Tuple<bool, Organization>(false, null)));
|
||||
// Act
|
||||
await _sut.ValidateAsync(context);
|
||||
|
||||
// Assert
|
||||
await _eventService.LogUserEventAsync(
|
||||
context.CustomValidatorRequestContext.User.Id, EventType.User_LoggedIn);
|
||||
await _userRepository.Received(1).ReplaceAsync(Arg.Any<User>());
|
||||
|
||||
Assert.False(context.GrantResult.IsError);
|
||||
}
|
||||
|
||||
/* Logic path
|
||||
ValidateAsync -> IsLegacyUser -> BuildErrorResultAsync
|
||||
*/
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_InvalidAuthType_ShouldSetSsoResult(
|
||||
public async Task ValidateAsync_DeviceValidated_ShouldSucceed(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||
CustomValidatorRequestContext requestContext,
|
||||
GrantValidationResult grantResult)
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||
|
||||
context.ValidatedTokenRequest.Raw["DeviceIdentifier"] = "DeviceIdentifier";
|
||||
context.ValidatedTokenRequest.Raw["DevicePushToken"] = "DevicePushToken";
|
||||
context.ValidatedTokenRequest.Raw["DeviceName"] = "DeviceName";
|
||||
context.ValidatedTokenRequest.Raw["DeviceType"] = "Android"; // This needs to be an actual Type
|
||||
// 1 -> to pass
|
||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||
_sut.isValid = true;
|
||||
|
||||
context.ValidatedTokenRequest.GrantType = "";
|
||||
// 2 -> will result to false with no extra configuration
|
||||
// 3 -> set two factor to be false
|
||||
_twoFactorAuthenticationValidator
|
||||
.RequiresTwoFactorAsync(Arg.Any<User>(), tokenRequest)
|
||||
.Returns(Task.FromResult(new Tuple<bool, Organization>(false, null)));
|
||||
|
||||
// 4 -> set up device validator to pass
|
||||
_deviceValidator.ValidateRequestDeviceAsync(Arg.Any<ValidatedTokenRequest>(), Arg.Any<CustomValidatorRequestContext>())
|
||||
.Returns(Task.FromResult(true));
|
||||
|
||||
// 5 -> not legacy user
|
||||
_userService.IsLegacyUser(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
await _sut.ValidateAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.False(context.GrantResult.IsError);
|
||||
}
|
||||
|
||||
// Test grantTypes that require SSO when a user is in an organization that requires it
|
||||
[Theory]
|
||||
[BitAutoData("password")]
|
||||
[BitAutoData("webauthn")]
|
||||
[BitAutoData("refresh_token")]
|
||||
public async Task ValidateAsync_GrantTypes_OrgSsoRequiredTrue_ShouldSetSsoResult(
|
||||
string grantType,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||
CustomValidatorRequestContext requestContext,
|
||||
GrantValidationResult grantResult)
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||
_sut.isValid = true;
|
||||
|
||||
context.ValidatedTokenRequest.GrantType = grantType;
|
||||
_policyService.AnyPoliciesApplicableToUserAsync(
|
||||
Arg.Any<Guid>(), PolicyType.RequireSso, OrganizationUserStatusType.Confirmed)
|
||||
.Returns(Task.FromResult(true));
|
||||
_twoFactorAuthenticationValidator
|
||||
.RequiresTwoFactorAsync(Arg.Any<User>(), Arg.Any<ValidatedTokenRequest>())
|
||||
.Returns(Task.FromResult(new Tuple<bool, Organization>(false, null)));
|
||||
|
||||
// Act
|
||||
await _sut.ValidateAsync(context);
|
||||
|
||||
@ -314,6 +276,85 @@ public class BaseRequestValidatorTests
|
||||
Assert.Equal("SSO authentication is required.", errorResponse.Message);
|
||||
}
|
||||
|
||||
// Test grantTypes where SSO would be required but the user is not in an
|
||||
// organization that requires it
|
||||
[Theory]
|
||||
[BitAutoData("password")]
|
||||
[BitAutoData("webauthn")]
|
||||
[BitAutoData("refresh_token")]
|
||||
public async Task ValidateAsync_GrantTypes_OrgSsoRequiredFalse_ShouldSucceed(
|
||||
string grantType,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||
CustomValidatorRequestContext requestContext,
|
||||
GrantValidationResult grantResult)
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||
_sut.isValid = true;
|
||||
|
||||
context.ValidatedTokenRequest.GrantType = grantType;
|
||||
|
||||
_policyService.AnyPoliciesApplicableToUserAsync(
|
||||
Arg.Any<Guid>(), PolicyType.RequireSso, OrganizationUserStatusType.Confirmed)
|
||||
.Returns(Task.FromResult(false));
|
||||
_twoFactorAuthenticationValidator.RequiresTwoFactorAsync(requestContext.User, tokenRequest)
|
||||
.Returns(Task.FromResult(new Tuple<bool, Organization>(false, null)));
|
||||
_deviceValidator.ValidateRequestDeviceAsync(tokenRequest, requestContext)
|
||||
.Returns(Task.FromResult(true));
|
||||
context.ValidatedTokenRequest.ClientId = "web";
|
||||
|
||||
// Act
|
||||
await _sut.ValidateAsync(context);
|
||||
|
||||
// Assert
|
||||
await _eventService.Received(1).LogUserEventAsync(
|
||||
context.CustomValidatorRequestContext.User.Id, EventType.User_LoggedIn);
|
||||
await _userRepository.Received(1).ReplaceAsync(Arg.Any<User>());
|
||||
|
||||
Assert.False(context.GrantResult.IsError);
|
||||
|
||||
}
|
||||
|
||||
// Test the grantTypes where SSO is in progress or not relevant
|
||||
[Theory]
|
||||
[BitAutoData("authorization_code")]
|
||||
[BitAutoData("client_credentials")]
|
||||
public async Task ValidateAsync_GrantTypes_SsoRequiredFalse_ShouldSucceed(
|
||||
string grantType,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||
CustomValidatorRequestContext requestContext,
|
||||
GrantValidationResult grantResult)
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||
_sut.isValid = true;
|
||||
|
||||
context.ValidatedTokenRequest.GrantType = grantType;
|
||||
|
||||
_twoFactorAuthenticationValidator.RequiresTwoFactorAsync(requestContext.User, tokenRequest)
|
||||
.Returns(Task.FromResult(new Tuple<bool, Organization>(false, null)));
|
||||
_deviceValidator.ValidateRequestDeviceAsync(tokenRequest, requestContext)
|
||||
.Returns(Task.FromResult(true));
|
||||
context.ValidatedTokenRequest.ClientId = "web";
|
||||
|
||||
// Act
|
||||
await _sut.ValidateAsync(context);
|
||||
|
||||
// Assert
|
||||
await _policyService.DidNotReceive().AnyPoliciesApplicableToUserAsync(
|
||||
Arg.Any<Guid>(), PolicyType.RequireSso, OrganizationUserStatusType.Confirmed);
|
||||
await _eventService.Received(1).LogUserEventAsync(
|
||||
context.CustomValidatorRequestContext.User.Id, EventType.User_LoggedIn);
|
||||
await _userRepository.Received(1).ReplaceAsync(Arg.Any<User>());
|
||||
|
||||
Assert.False(context.GrantResult.IsError);
|
||||
}
|
||||
|
||||
/* Logic Path
|
||||
* ValidateAsync -> UserService.IsLegacyUser -> FailAuthForLegacyUserAsync
|
||||
*/
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_IsLegacyUser_FailAuthForLegacyUserAsync(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||
@ -332,6 +373,8 @@ public class BaseRequestValidatorTests
|
||||
_twoFactorAuthenticationValidator
|
||||
.RequiresTwoFactorAsync(Arg.Any<User>(), Arg.Any<ValidatedTokenRequest>())
|
||||
.Returns(Task.FromResult(new Tuple<bool, Organization>(false, null)));
|
||||
_deviceValidator.ValidateRequestDeviceAsync(tokenRequest, requestContext)
|
||||
.Returns(Task.FromResult(true));
|
||||
|
||||
// Act
|
||||
await _sut.ValidateAsync(context);
|
||||
@ -339,8 +382,9 @@ public class BaseRequestValidatorTests
|
||||
// Assert
|
||||
Assert.True(context.GrantResult.IsError);
|
||||
var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
|
||||
Assert.Equal($"Encryption key migration is required. Please log in to the web vault at {_globalSettings.BaseServiceUri.VaultWithHash}"
|
||||
, errorResponse.Message);
|
||||
var expectedMessage = $"Encryption key migration is required. Please log in to the web " +
|
||||
$"vault at {_globalSettings.BaseServiceUri.VaultWithHash}";
|
||||
Assert.Equal(expectedMessage, errorResponse.Message);
|
||||
}
|
||||
|
||||
private BaseRequestValidationContextFake CreateContext(
|
||||
@ -367,4 +411,12 @@ public class BaseRequestValidatorTests
|
||||
Substitute.For<IServiceProvider>(),
|
||||
Substitute.For<ILogger<UserManager<User>>>());
|
||||
}
|
||||
|
||||
private void AddValidDeviceToRequest(ValidatedTokenRequest request)
|
||||
{
|
||||
request.Raw["DeviceIdentifier"] = "DeviceIdentifier";
|
||||
request.Raw["DeviceType"] = "Android"; // must be valid device type
|
||||
request.Raw["DeviceName"] = "DeviceName";
|
||||
request.Raw["DevicePushToken"] = "DevicePushToken";
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Identity.IdentityServer;
|
||||
using Bit.Identity.IdentityServer.RequestValidators;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Duende.IdentityServer.Validation;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using AuthFixtures = Bit.Identity.Test.AutoFixture;
|
||||
@ -20,6 +25,10 @@ public class DeviceValidatorTests
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IMailService _mailService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IDistributedCache _distributedCache;
|
||||
private readonly Logger<DeviceValidator> _logger;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly DeviceValidator _sut;
|
||||
|
||||
public DeviceValidatorTests()
|
||||
@ -29,219 +38,582 @@ public class DeviceValidatorTests
|
||||
_globalSettings = new GlobalSettings();
|
||||
_mailService = Substitute.For<IMailService>();
|
||||
_currentContext = Substitute.For<ICurrentContext>();
|
||||
_userService = Substitute.For<IUserService>();
|
||||
_distributedCache = Substitute.For<IDistributedCache>();
|
||||
_logger = new Logger<DeviceValidator>(Substitute.For<ILoggerFactory>());
|
||||
_featureService = Substitute.For<IFeatureService>();
|
||||
_sut = new DeviceValidator(
|
||||
_deviceService,
|
||||
_deviceRepository,
|
||||
_globalSettings,
|
||||
_mailService,
|
||||
_currentContext);
|
||||
_currentContext,
|
||||
_userService,
|
||||
_distributedCache,
|
||||
_logger,
|
||||
_featureService);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void SaveDeviceAsync_DeviceNull_ShouldReturnNull(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request,
|
||||
User user)
|
||||
{
|
||||
// Arrange
|
||||
request.Raw["DeviceIdentifier"] = null;
|
||||
|
||||
// Act
|
||||
var device = await _sut.SaveDeviceAsync(user, request);
|
||||
|
||||
// Assert
|
||||
Assert.Null(device);
|
||||
await _mailService.DidNotReceive().SendNewDeviceLoggedInEmail(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void SaveDeviceAsync_UserIsNull_ShouldReturnNull(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
request = AddValidDeviceToRequest(request);
|
||||
|
||||
// Act
|
||||
var device = await _sut.SaveDeviceAsync(null, request);
|
||||
|
||||
// Assert
|
||||
Assert.Null(device);
|
||||
await _mailService.DidNotReceive().SendNewDeviceLoggedInEmail(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void SaveDeviceAsync_ExistingUser_NewDevice_ReturnsDevice_SendsEmail(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request,
|
||||
User user)
|
||||
{
|
||||
// Arrange
|
||||
request = AddValidDeviceToRequest(request);
|
||||
|
||||
user.CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(11);
|
||||
_globalSettings.DisableEmailNewDevice = false;
|
||||
|
||||
// Act
|
||||
var device = await _sut.SaveDeviceAsync(user, request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(device);
|
||||
Assert.Equal(user.Id, device.UserId);
|
||||
Assert.Equal("DeviceIdentifier", device.Identifier);
|
||||
Assert.Equal(DeviceType.Android, device.Type);
|
||||
await _mailService.Received(1).SendNewDeviceLoggedInEmail(
|
||||
user.Email, "Android", Arg.Any<DateTime>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void SaveDeviceAsync_ExistingUser_NewDevice_ReturnsDevice_SendEmailFalse(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request,
|
||||
User user)
|
||||
{
|
||||
// Arrange
|
||||
request = AddValidDeviceToRequest(request);
|
||||
|
||||
user.CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(11);
|
||||
_globalSettings.DisableEmailNewDevice = true;
|
||||
|
||||
// Act
|
||||
var device = await _sut.SaveDeviceAsync(user, request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(device);
|
||||
Assert.Equal(user.Id, device.UserId);
|
||||
Assert.Equal("DeviceIdentifier", device.Identifier);
|
||||
Assert.Equal(DeviceType.Android, device.Type);
|
||||
await _mailService.DidNotReceive().SendNewDeviceLoggedInEmail(
|
||||
user.Email, "Android", Arg.Any<DateTime>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void SaveDeviceAsync_DeviceIsKnown_ShouldReturnDevice(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request,
|
||||
User user,
|
||||
[Theory, BitAutoData]
|
||||
public async void GetKnownDeviceAsync_UserNull_ReturnsFalse(
|
||||
Device device)
|
||||
{
|
||||
// Arrange
|
||||
request = AddValidDeviceToRequest(request);
|
||||
|
||||
device.UserId = user.Id;
|
||||
device.Identifier = "DeviceIdentifier";
|
||||
device.Type = DeviceType.Android;
|
||||
device.Name = "DeviceName";
|
||||
device.PushToken = "DevicePushToken";
|
||||
_deviceRepository.GetByIdentifierAsync(device.Identifier, user.Id).Returns(device);
|
||||
// AutoData arranges
|
||||
|
||||
// Act
|
||||
var resultDevice = await _sut.SaveDeviceAsync(user, request);
|
||||
var result = await _sut.GetKnownDeviceAsync(null, device);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(device, resultDevice);
|
||||
await _mailService.DidNotReceive().SendNewDeviceLoggedInEmail(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>());
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void SaveDeviceAsync_NewUser_DeviceUnknown_ShouldSaveDevice_NoEmail(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request,
|
||||
User user)
|
||||
{
|
||||
// Arrange
|
||||
request = AddValidDeviceToRequest(request);
|
||||
user.CreationDate = DateTime.UtcNow;
|
||||
_deviceRepository.GetByIdentifierAsync(Arg.Any<string>(), Arg.Any<Guid>()).Returns(null as Device);
|
||||
|
||||
// Act
|
||||
var device = await _sut.SaveDeviceAsync(user, request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(device);
|
||||
Assert.Equal(user.Id, device.UserId);
|
||||
Assert.Equal("DeviceIdentifier", device.Identifier);
|
||||
Assert.Equal(DeviceType.Android, device.Type);
|
||||
await _deviceService.Received(1).SaveAsync(device);
|
||||
await _mailService.DidNotReceive().SendNewDeviceLoggedInEmail(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void KnownDeviceAsync_UserNull_ReturnsFalse(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
request = AddValidDeviceToRequest(request);
|
||||
|
||||
// Act
|
||||
var result = await _sut.KnownDeviceAsync(null, request);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void KnownDeviceAsync_DeviceNull_ReturnsFalse(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request,
|
||||
[Theory, BitAutoData]
|
||||
public async void GetKnownDeviceAsync_DeviceNull_ReturnsFalse(
|
||||
User user)
|
||||
{
|
||||
// Arrange
|
||||
// Device raw data is null which will cause the device to be null
|
||||
|
||||
// Act
|
||||
var result = await _sut.KnownDeviceAsync(user, request);
|
||||
var result = await _sut.GetKnownDeviceAsync(user, null);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void KnownDeviceAsync_DeviceNotInDatabase_ReturnsFalse(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request,
|
||||
User user)
|
||||
[Theory, BitAutoData]
|
||||
public async void GetKnownDeviceAsync_DeviceNotInDatabase_ReturnsFalse(
|
||||
User user,
|
||||
Device device)
|
||||
{
|
||||
// Arrange
|
||||
request = AddValidDeviceToRequest(request);
|
||||
_deviceRepository.GetByIdentifierAsync(Arg.Any<string>(), Arg.Any<Guid>())
|
||||
.Returns(null as Device);
|
||||
// Act
|
||||
var result = await _sut.KnownDeviceAsync(user, request);
|
||||
var result = await _sut.GetKnownDeviceAsync(user, device);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async void KnownDeviceAsync_UserAndDeviceValid_ReturnsTrue(
|
||||
[Theory, BitAutoData]
|
||||
public async void GetKnownDeviceAsync_UserAndDeviceValid_ReturnsTrue(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request,
|
||||
User user,
|
||||
Device device)
|
||||
{
|
||||
// Arrange
|
||||
request = AddValidDeviceToRequest(request);
|
||||
AddValidDeviceToRequest(request);
|
||||
_deviceRepository.GetByIdentifierAsync(Arg.Any<string>(), Arg.Any<Guid>())
|
||||
.Returns(device);
|
||||
// Act
|
||||
var result = await _sut.KnownDeviceAsync(user, request);
|
||||
var result = await _sut.GetKnownDeviceAsync(user, device);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData("not null", "Android", "")]
|
||||
[BitAutoData("not null", "", "not null")]
|
||||
[BitAutoData("", "Android", "not null")]
|
||||
public void GetDeviceFromRequest_RawDeviceInfoNull_ReturnsNull(
|
||||
string deviceIdentifier,
|
||||
string deviceType,
|
||||
string deviceName,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
request.Raw["DeviceIdentifier"] = deviceIdentifier;
|
||||
request.Raw["DeviceType"] = deviceType;
|
||||
request.Raw["DeviceName"] = deviceName;
|
||||
|
||||
// Act
|
||||
var result = DeviceValidator.GetDeviceFromRequest(request);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void GetDeviceFromRequest_RawDeviceInfoValid_ReturnsDevice(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
AddValidDeviceToRequest(request);
|
||||
|
||||
// Act
|
||||
var result = DeviceValidator.GetDeviceFromRequest(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("DeviceIdentifier", result.Identifier);
|
||||
Assert.Equal("DeviceName", result.Name);
|
||||
Assert.Equal(DeviceType.Android, result.Type);
|
||||
Assert.Equal("DevicePushToken", result.PushToken);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void ValidateRequestDeviceAsync_DeviceNull_ContextModified_ReturnsFalse(
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
context.KnownDevice = false;
|
||||
context.Device = null;
|
||||
|
||||
// Act
|
||||
Assert.NotNull(context.User);
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _deviceService.Received(0).SaveAsync(Arg.Any<Device>());
|
||||
|
||||
Assert.False(result);
|
||||
Assert.NotNull(context.CustomResponse["ErrorModel"]);
|
||||
var expectedErrorModel = new ErrorResponseModel("no device information provided");
|
||||
var actualResponse = (ErrorResponseModel)context.CustomResponse["ErrorModel"];
|
||||
Assert.Equal(expectedErrorModel.Message, actualResponse.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void ValidateRequestDeviceAsync_RequestDeviceKnown_ContextDeviceModified_ReturnsTrue(
|
||||
Device device,
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
context.KnownDevice = false;
|
||||
context.Device = null;
|
||||
AddValidDeviceToRequest(request);
|
||||
_deviceRepository.GetByIdentifierAsync(Arg.Any<string>(), Arg.Any<Guid>())
|
||||
.Returns(device);
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _deviceService.Received(0).SaveAsync(Arg.Any<Device>());
|
||||
|
||||
Assert.True(result);
|
||||
Assert.False(context.CustomResponse.ContainsKey("ErrorModel"));
|
||||
Assert.NotNull(context.Device);
|
||||
Assert.Equal(context.Device, device);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void ValidateRequestDeviceAsync_ContextDeviceKnown_ContextDeviceModified_ReturnsTrue(
|
||||
Device databaseDevice,
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
context.KnownDevice = false;
|
||||
_deviceRepository.GetByIdentifierAsync(Arg.Any<string>(), Arg.Any<Guid>())
|
||||
.Returns(databaseDevice);
|
||||
// we want to show that the context device is updated when the device is known
|
||||
Assert.NotEqual(context.Device, databaseDevice);
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _deviceService.Received(0).SaveAsync(Arg.Any<Device>());
|
||||
|
||||
Assert.True(result);
|
||||
Assert.False(context.CustomResponse.ContainsKey("ErrorModel"));
|
||||
Assert.Equal(context.Device, databaseDevice);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void ValidateRequestDeviceAsync_NewDeviceVerificationFeatureFlagFalse_SendsEmail_ReturnsTrue(
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
context.KnownDevice = false;
|
||||
AddValidDeviceToRequest(request);
|
||||
_globalSettings.DisableEmailNewDevice = false;
|
||||
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
||||
.Returns(null as Device);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
|
||||
.Returns(false);
|
||||
// set user creation to more than 10 minutes ago
|
||||
context.User.CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(11);
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _deviceService.Received(1).SaveAsync(context.Device);
|
||||
await _mailService.Received(1).SendNewDeviceLoggedInEmail(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>());
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void ValidateRequestDeviceAsync_NewDeviceVerificationFeatureFlagFalse_NewUser_DoesNotSendEmail_ReturnsTrue(
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
context.KnownDevice = false;
|
||||
AddValidDeviceToRequest(request);
|
||||
_globalSettings.DisableEmailNewDevice = false;
|
||||
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
||||
.Returns(null as Device);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
|
||||
.Returns(false);
|
||||
// set user creation to less than 10 minutes ago
|
||||
context.User.CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(9);
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _deviceService.Received(1).SaveAsync(context.Device);
|
||||
await _mailService.Received(0).SendNewDeviceLoggedInEmail(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>());
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void ValidateRequestDeviceAsync_NewDeviceVerificationFeatureFlagFalse_DisableEmailTrue_DoesNotSendEmail_ReturnsTrue(
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
context.KnownDevice = false;
|
||||
AddValidDeviceToRequest(request);
|
||||
_globalSettings.DisableEmailNewDevice = true;
|
||||
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
||||
.Returns(null as Device);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _deviceService.Received(1).SaveAsync(context.Device);
|
||||
await _mailService.Received(0).SendNewDeviceLoggedInEmail(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>());
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData("webauthn")]
|
||||
[BitAutoData("refresh_token")]
|
||||
[BitAutoData("authorization_code")]
|
||||
[BitAutoData("client_credentials")]
|
||||
public async void ValidateRequestDeviceAsync_GrantTypeNotPassword_SavesDevice_ReturnsTrue(
|
||||
string grantType,
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
context.KnownDevice = false;
|
||||
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
||||
AddValidDeviceToRequest(request);
|
||||
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
||||
.Returns(null as Device);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
|
||||
.Returns(true);
|
||||
|
||||
request.GrantType = grantType;
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _deviceService.Received(1).SaveAsync(context.Device);
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void ValidateRequestDeviceAsync_IsAuthRequest_SavesDevice_ReturnsTrue(
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
context.KnownDevice = false;
|
||||
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
||||
AddValidDeviceToRequest(request);
|
||||
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
||||
.Returns(null as Device);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
|
||||
.Returns(true);
|
||||
|
||||
request.Raw.Add("AuthRequest", "authRequest");
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _deviceService.Received(1).SaveAsync(context.Device);
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void ValidateRequestDeviceAsync_TwoFactorRequired_SavesDevice_ReturnsTrue(
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
context.KnownDevice = false;
|
||||
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
||||
AddValidDeviceToRequest(request);
|
||||
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
||||
.Returns(null as Device);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
|
||||
.Returns(true);
|
||||
|
||||
context.TwoFactorRequired = true;
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _deviceService.Received(1).SaveAsync(context.Device);
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void ValidateRequestDeviceAsync_SsoRequired_SavesDevice_ReturnsTrue(
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
context.KnownDevice = false;
|
||||
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
||||
AddValidDeviceToRequest(request);
|
||||
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
||||
.Returns(null as Device);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
|
||||
.Returns(true);
|
||||
|
||||
context.SsoRequired = true;
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _deviceService.Received(1).SaveAsync(context.Device);
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void HandleNewDeviceVerificationAsync_UserNull_ContextModified_ReturnsInvalidUser(
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true);
|
||||
_globalSettings.EnableNewDeviceVerification = true;
|
||||
|
||||
context.User = null;
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _deviceService.Received(0).SaveAsync(Arg.Any<Device>());
|
||||
|
||||
Assert.False(result);
|
||||
Assert.NotNull(context.CustomResponse["ErrorModel"]);
|
||||
// PM-13340: The error message should be "invalid user" instead of "no device information provided"
|
||||
var expectedErrorMessage = "no device information provided";
|
||||
var actualResponse = (ErrorResponseModel)context.CustomResponse["ErrorModel"];
|
||||
Assert.Equal(expectedErrorMessage, actualResponse.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void HandleNewDeviceVerificationAsync_UserHasCacheValue_ReturnsSuccess(
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true);
|
||||
_globalSettings.EnableNewDeviceVerification = true;
|
||||
_distributedCache.GetAsync(Arg.Any<string>()).Returns([1]);
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _userService.Received(0).SendOTPAsync(context.User);
|
||||
await _deviceService.Received(1).SaveAsync(Arg.Any<Device>());
|
||||
|
||||
Assert.True(result);
|
||||
Assert.False(context.CustomResponse.ContainsKey("ErrorModel"));
|
||||
Assert.Equal(context.User.Id, context.Device.UserId);
|
||||
Assert.NotNull(context.Device);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void HandleNewDeviceVerificationAsync_NewDeviceOtpValid_ReturnsSuccess(
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true);
|
||||
_globalSettings.EnableNewDeviceVerification = true;
|
||||
_distributedCache.GetAsync(Arg.Any<string>()).Returns(null as byte[]);
|
||||
|
||||
var newDeviceOtp = "123456";
|
||||
request.Raw.Add("NewDeviceOtp", newDeviceOtp);
|
||||
|
||||
_userService.VerifyOTPAsync(context.User, newDeviceOtp).Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _userService.Received(0).SendOTPAsync(context.User);
|
||||
await _deviceService.Received(1).SaveAsync(context.Device);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.False(context.CustomResponse.ContainsKey("ErrorModel"));
|
||||
Assert.Equal(context.User.Id, context.Device.UserId);
|
||||
Assert.NotNull(context.Device);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData("")]
|
||||
[BitAutoData("123456")]
|
||||
public async void HandleNewDeviceVerificationAsync_NewDeviceOtpInvalid_ReturnsInvalidNewDeviceOtp(
|
||||
string newDeviceOtp,
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true);
|
||||
_globalSettings.EnableNewDeviceVerification = true;
|
||||
_distributedCache.GetAsync(Arg.Any<string>()).Returns(null as byte[]);
|
||||
|
||||
request.Raw.Add("NewDeviceOtp", newDeviceOtp);
|
||||
|
||||
_userService.VerifyOTPAsync(context.User, newDeviceOtp).Returns(false);
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _userService.DidNotReceive().SendOTPAsync(Arg.Any<User>());
|
||||
await _deviceService.Received(0).SaveAsync(Arg.Any<Device>());
|
||||
|
||||
Assert.False(result);
|
||||
Assert.NotNull(context.CustomResponse["ErrorModel"]);
|
||||
var expectedErrorMessage = "invalid new device otp";
|
||||
var actualResponse = (ErrorResponseModel)context.CustomResponse["ErrorModel"];
|
||||
Assert.Equal(expectedErrorMessage, actualResponse.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void HandleNewDeviceVerificationAsync_UserHasNoDevices_ReturnsSuccess(
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true);
|
||||
_globalSettings.EnableNewDeviceVerification = true;
|
||||
_distributedCache.GetAsync(Arg.Any<string>()).Returns([1]);
|
||||
_deviceRepository.GetManyByUserIdAsync(context.User.Id).Returns([]);
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _userService.Received(0).VerifyOTPAsync(Arg.Any<User>(), Arg.Any<string>());
|
||||
await _userService.Received(0).SendOTPAsync(Arg.Any<User>());
|
||||
await _deviceService.Received(1).SaveAsync(context.Device);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.False(context.CustomResponse.ContainsKey("ErrorModel"));
|
||||
Assert.Equal(context.User.Id, context.Device.UserId);
|
||||
Assert.NotNull(context.Device);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void HandleNewDeviceVerificationAsync_NewDeviceOtpEmpty_UserHasDevices_ReturnsNewDeviceVerificationRequired(
|
||||
CustomValidatorRequestContext context,
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true);
|
||||
_globalSettings.EnableNewDeviceVerification = true;
|
||||
_deviceRepository.GetManyByUserIdAsync(context.User.Id).Returns([new Device()]);
|
||||
_distributedCache.GetAsync(Arg.Any<string>()).Returns(null as byte[]);
|
||||
|
||||
// Act
|
||||
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
||||
|
||||
// Assert
|
||||
await _userService.Received(1).SendOTPAsync(context.User);
|
||||
await _deviceService.Received(0).SaveAsync(Arg.Any<Device>());
|
||||
|
||||
Assert.False(result);
|
||||
Assert.NotNull(context.CustomResponse["ErrorModel"]);
|
||||
var expectedErrorMessage = "new device verification required";
|
||||
var actualResponse = (ErrorResponseModel)context.CustomResponse["ErrorModel"];
|
||||
Assert.Equal(expectedErrorMessage, actualResponse.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void NewDeviceOtpRequest_NewDeviceOtpNull_ReturnsFalse(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
// Autodata arranges
|
||||
|
||||
// Act
|
||||
var result = DeviceValidator.NewDeviceOtpRequest(request);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void NewDeviceOtpRequest_NewDeviceOtpNotNull_ReturnsTrue(
|
||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
||||
{
|
||||
// Arrange
|
||||
request.Raw["NewDeviceOtp"] = "123456";
|
||||
|
||||
// Act
|
||||
var result = DeviceValidator.NewDeviceOtpRequest(request);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
private ValidatedTokenRequest AddValidDeviceToRequest(ValidatedTokenRequest request)
|
||||
private static void AddValidDeviceToRequest(ValidatedTokenRequest request)
|
||||
{
|
||||
request.Raw["DeviceIdentifier"] = "DeviceIdentifier";
|
||||
request.Raw["DeviceType"] = "Android";
|
||||
request.Raw["DeviceType"] = "Android"; // must be valid device type
|
||||
request.Raw["DeviceName"] = "DeviceName";
|
||||
request.Raw["DevicePushToken"] = "DevicePushToken";
|
||||
return request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the request context to facilitate testing the HandleNewDeviceVerificationAsync method.
|
||||
/// </summary>
|
||||
/// <param name="context">test context</param>
|
||||
/// <param name="request">test request</param>
|
||||
private static void ArrangeForHandleNewDeviceVerificationTest(
|
||||
CustomValidatorRequestContext context,
|
||||
ValidatedTokenRequest request)
|
||||
{
|
||||
context.KnownDevice = false;
|
||||
request.GrantType = "password";
|
||||
context.TwoFactorRequired = false;
|
||||
context.SsoRequired = false;
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +123,11 @@ IBaseRequestValidatorTestWrapper
|
||||
Dictionary<string, object> customResponse)
|
||||
{ }
|
||||
|
||||
protected override void SetValidationErrorResult(
|
||||
BaseRequestValidationContextFake context,
|
||||
CustomValidatorRequestContext requestContext)
|
||||
{ }
|
||||
|
||||
protected override Task<bool> ValidateContextAsync(
|
||||
BaseRequestValidationContextFake context,
|
||||
CustomValidatorRequestContext validatorContext)
|
||||
|
@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
@ -255,9 +255,8 @@ public class OrganizationUserRepositoryTests
|
||||
Assert.Equal(organization.SmServiceAccounts, result.SmServiceAccounts);
|
||||
Assert.Equal(organization.LimitCollectionCreation, result.LimitCollectionCreation);
|
||||
Assert.Equal(organization.LimitCollectionDeletion, result.LimitCollectionDeletion);
|
||||
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
|
||||
Assert.Equal(organization.LimitCollectionCreationDeletion, result.LimitCollectionCreationDeletion);
|
||||
Assert.Equal(organization.AllowAdminAccessToAllCollectionItems, result.AllowAdminAccessToAllCollectionItems);
|
||||
Assert.Equal(organization.UseRiskInsights, result.UseRiskInsights);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
|
@ -0,0 +1,99 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest.Repositories;
|
||||
|
||||
public class UserRepositoryTests
|
||||
{
|
||||
[DatabaseTheory, DatabaseData]
|
||||
public async Task DeleteAsync_Works(IUserRepository userRepository)
|
||||
{
|
||||
var user = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test User",
|
||||
Email = $"test+{Guid.NewGuid()}@example.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
|
||||
await userRepository.DeleteAsync(user);
|
||||
|
||||
var deletedUser = await userRepository.GetByIdAsync(user.Id);
|
||||
Assert.Null(deletedUser);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
public async Task DeleteManyAsync_Works(IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, IOrganizationRepository organizationRepository)
|
||||
{
|
||||
var user1 = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test User 1",
|
||||
Email = $"test+{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
|
||||
var user2 = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test User 2",
|
||||
Email = $"test+{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
|
||||
var user3 = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test User 3",
|
||||
Email = $"test+{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = "Test Org",
|
||||
BillingEmail = user3.Email, // TODO: EF does not enfore this being NOT NULL
|
||||
Plan = "Test", // TODO: EF does not enforce this being NOT NULl
|
||||
});
|
||||
|
||||
await organizationUserRepository.CreateAsync(new OrganizationUser
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
UserId = user1.Id,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
});
|
||||
|
||||
await organizationUserRepository.CreateAsync(new OrganizationUser
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
UserId = user3.Id,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
});
|
||||
|
||||
await userRepository.DeleteManyAsync(new List<User>
|
||||
{
|
||||
user1,
|
||||
user2
|
||||
});
|
||||
|
||||
var deletedUser1 = await userRepository.GetByIdAsync(user1.Id);
|
||||
var deletedUser2 = await userRepository.GetByIdAsync(user2.Id);
|
||||
var notDeletedUser3 = await userRepository.GetByIdAsync(user3.Id);
|
||||
|
||||
var orgUser1Deleted = await organizationUserRepository.GetByIdAsync(user1.Id);
|
||||
|
||||
var notDeletedOrgUsers = await organizationUserRepository.GetManyByUserAsync(user3.Id);
|
||||
|
||||
Assert.Null(deletedUser1);
|
||||
Assert.Null(deletedUser2);
|
||||
Assert.NotNull(notDeletedUser3);
|
||||
|
||||
Assert.Null(orgUser1Deleted);
|
||||
Assert.NotNull(notDeletedOrgUsers);
|
||||
Assert.True(notDeletedOrgUsers.Count > 0);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Bit.Core.Vault.Entities;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest.Comparers;
|
||||
|
||||
/// <summary>
|
||||
/// Determines the equality of two SecurityTask objects.
|
||||
/// </summary>
|
||||
public class SecurityTaskComparer : IEqualityComparer<SecurityTask>
|
||||
{
|
||||
public bool Equals(SecurityTask x, SecurityTask y)
|
||||
{
|
||||
return x.Id.Equals(y.Id) &&
|
||||
x.Type.Equals(y.Type) &&
|
||||
x.Status.Equals(y.Status);
|
||||
}
|
||||
|
||||
public int GetHashCode([DisallowNull] SecurityTask obj)
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
@ -1,9 +1,13 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Infrastructure.IntegrationTest.Comparers;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest.Vault.Repositories;
|
||||
@ -120,4 +124,103 @@ public class SecurityTaskRepositoryTests
|
||||
Assert.Equal(task.Id, updatedTask.Id);
|
||||
Assert.Equal(SecurityTaskStatus.Completed, updatedTask.Status);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
public async Task GetManyByUserIdAsync_ReturnsExpectedTasks(
|
||||
IUserRepository userRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
ICipherRepository cipherRepository,
|
||||
ISecurityTaskRepository securityTaskRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
ICollectionRepository collectionRepository)
|
||||
{
|
||||
var user = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test User",
|
||||
Email = $"test+{Guid.NewGuid()}@email.com",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
});
|
||||
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = "Test Org",
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
Plan = "Test Plan",
|
||||
BillingEmail = "billing@email.com"
|
||||
});
|
||||
|
||||
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
UserId = user.Id,
|
||||
Status = OrganizationUserStatusType.Confirmed
|
||||
});
|
||||
|
||||
var collection = await collectionRepository.CreateAsync(new Collection
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Name = "Test Collection 1",
|
||||
});
|
||||
|
||||
var collection2 = await collectionRepository.CreateAsync(new Collection
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Name = "Test Collection 2",
|
||||
});
|
||||
|
||||
var cipher1 = new Cipher { Type = CipherType.Login, OrganizationId = organization.Id, Data = "", };
|
||||
await cipherRepository.CreateAsync(cipher1, [collection.Id, collection2.Id]);
|
||||
|
||||
var cipher2 = new Cipher { Type = CipherType.Login, OrganizationId = organization.Id, Data = "", };
|
||||
await cipherRepository.CreateAsync(cipher2, [collection.Id]);
|
||||
|
||||
var task1 = await securityTaskRepository.CreateAsync(new SecurityTask
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
CipherId = cipher1.Id,
|
||||
Status = SecurityTaskStatus.Pending,
|
||||
Type = SecurityTaskType.UpdateAtRiskCredential,
|
||||
});
|
||||
|
||||
var task2 = await securityTaskRepository.CreateAsync(new SecurityTask
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
CipherId = cipher2.Id,
|
||||
Status = SecurityTaskStatus.Completed,
|
||||
Type = SecurityTaskType.UpdateAtRiskCredential,
|
||||
});
|
||||
|
||||
var task3 = await securityTaskRepository.CreateAsync(new SecurityTask
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
CipherId = cipher2.Id,
|
||||
Status = SecurityTaskStatus.Pending,
|
||||
Type = SecurityTaskType.UpdateAtRiskCredential,
|
||||
});
|
||||
|
||||
await collectionRepository.UpdateUsersAsync(collection.Id,
|
||||
new List<CollectionAccessSelection>
|
||||
{
|
||||
new() {Id = orgUser.Id, ReadOnly = false, HidePasswords = false, Manage = true}
|
||||
});
|
||||
|
||||
var allTasks = await securityTaskRepository.GetManyByUserIdStatusAsync(user.Id);
|
||||
Assert.Equal(3, allTasks.Count);
|
||||
Assert.Contains(task1, allTasks, new SecurityTaskComparer());
|
||||
Assert.Contains(task2, allTasks, new SecurityTaskComparer());
|
||||
Assert.Contains(task3, allTasks, new SecurityTaskComparer());
|
||||
|
||||
var pendingTasks = await securityTaskRepository.GetManyByUserIdStatusAsync(user.Id, SecurityTaskStatus.Pending);
|
||||
Assert.Equal(2, pendingTasks.Count);
|
||||
Assert.Contains(task1, pendingTasks, new SecurityTaskComparer());
|
||||
Assert.Contains(task3, pendingTasks, new SecurityTaskComparer());
|
||||
Assert.DoesNotContain(task2, pendingTasks, new SecurityTaskComparer());
|
||||
|
||||
var completedTasks = await securityTaskRepository.GetManyByUserIdStatusAsync(user.Id, SecurityTaskStatus.Completed);
|
||||
Assert.Single(completedTasks);
|
||||
Assert.Contains(task2, completedTasks, new SecurityTaskComparer());
|
||||
Assert.DoesNotContain(task1, completedTasks, new SecurityTaskComparer());
|
||||
Assert.DoesNotContain(task3, completedTasks, new SecurityTaskComparer());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user