mirror of
https://github.com/bitwarden/server
synced 2026-01-28 15:23:38 +00:00
Modified test cases to use updated values for MinComplexity (4) and MinLength (128). Added new tests to verify that excessive values for these properties return BadRequest responses. Ensured consistency across integration tests for both Admin and Public controllers.
445 lines
14 KiB
C#
445 lines
14 KiB
C#
using System.Net;
|
|
using System.Text.Json;
|
|
using Bit.Api.AdminConsole.Models.Request;
|
|
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
|
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.Core.Repositories;
|
|
using Bit.Test.Common.Helpers;
|
|
using NSubstitute;
|
|
using Xunit;
|
|
|
|
namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
|
|
|
|
public class PoliciesControllerTests : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
|
|
{
|
|
private readonly HttpClient _client;
|
|
private readonly ApiApplicationFactory _factory;
|
|
private readonly LoginHelper _loginHelper;
|
|
|
|
private Organization _organization = null!;
|
|
private string _ownerEmail = null!;
|
|
|
|
public PoliciesControllerTests(ApiApplicationFactory factory)
|
|
{
|
|
_factory = factory;
|
|
_factory.SubstituteService<Core.Services.IFeatureService>(featureService =>
|
|
{
|
|
featureService
|
|
.IsEnabled("pm-19467-create-default-location")
|
|
.Returns(true);
|
|
});
|
|
_client = factory.CreateClient();
|
|
_loginHelper = new LoginHelper(_factory, _client);
|
|
}
|
|
|
|
public async Task InitializeAsync()
|
|
{
|
|
_ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
|
await _factory.LoginWithNewAccount(_ownerEmail);
|
|
|
|
(_organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually,
|
|
ownerEmail: _ownerEmail, passwordManagerSeats: 10, paymentMethod: PaymentMethodType.Card);
|
|
|
|
await _loginHelper.LoginAsync(_ownerEmail);
|
|
}
|
|
|
|
public Task DisposeAsync()
|
|
{
|
|
_client.Dispose();
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PutVNext_OrganizationDataOwnershipPolicy_Success()
|
|
{
|
|
// Arrange
|
|
const PolicyType policyType = PolicyType.OrganizationDataOwnership;
|
|
|
|
const string defaultCollectionName = "Test Default Collection";
|
|
var request = new SavePolicyRequest
|
|
{
|
|
Policy = new PolicyRequestModel
|
|
{
|
|
Enabled = true,
|
|
},
|
|
Metadata = new Dictionary<string, object>
|
|
{
|
|
{ "defaultUserCollectionName", defaultCollectionName }
|
|
}
|
|
};
|
|
|
|
var (_, admin) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
|
_organization.Id, OrganizationUserType.Admin);
|
|
|
|
var (_, user) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
|
_organization.Id, OrganizationUserType.User);
|
|
|
|
// Act
|
|
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}/vnext",
|
|
JsonContent.Create(request));
|
|
|
|
// Assert
|
|
await AssertResponse();
|
|
|
|
await AssertPolicy();
|
|
|
|
await AssertDefaultCollectionCreatedOnlyForUserTypeAsync();
|
|
return;
|
|
|
|
async Task AssertDefaultCollectionCreatedOnlyForUserTypeAsync()
|
|
{
|
|
var collectionRepository = _factory.GetService<ICollectionRepository>();
|
|
await AssertUserExpectations(collectionRepository);
|
|
await AssertAdminExpectations(collectionRepository);
|
|
}
|
|
|
|
async Task AssertUserExpectations(ICollectionRepository collectionRepository)
|
|
{
|
|
var collections = await collectionRepository.GetManyByUserIdAsync(user.UserId!.Value);
|
|
var defaultCollection = collections.FirstOrDefault(c => c.Name == defaultCollectionName);
|
|
Assert.NotNull(defaultCollection);
|
|
Assert.Equal(_organization.Id, defaultCollection.OrganizationId);
|
|
}
|
|
|
|
async Task AssertAdminExpectations(ICollectionRepository collectionRepository)
|
|
{
|
|
var collections = await collectionRepository.GetManyByUserIdAsync(admin.UserId!.Value);
|
|
var defaultCollection = collections.FirstOrDefault(c => c.Name == defaultCollectionName);
|
|
Assert.Null(defaultCollection);
|
|
}
|
|
|
|
async Task AssertResponse()
|
|
{
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var content = await response.Content.ReadFromJsonAsync<PolicyResponseModel>();
|
|
|
|
Assert.True(content.Enabled);
|
|
Assert.Equal(policyType, content.Type);
|
|
Assert.Equal(_organization.Id, content.OrganizationId);
|
|
}
|
|
|
|
async Task AssertPolicy()
|
|
{
|
|
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.Null(policy.Data);
|
|
Assert.Equal(_organization.Id, policy.OrganizationId);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PutVNext_MasterPasswordPolicy_Success()
|
|
{
|
|
// Arrange
|
|
var policyType = PolicyType.MasterPassword;
|
|
var request = new SavePolicyRequest
|
|
{
|
|
Policy = new PolicyRequestModel
|
|
{
|
|
Enabled = true,
|
|
Data = new Dictionary<string, object>
|
|
{
|
|
{ "minComplexity", 4 },
|
|
{ "minLength", 128 },
|
|
{ "requireUpper", true },
|
|
{ "requireLower", false },
|
|
{ "requireNumbers", true },
|
|
{ "requireSpecial", false },
|
|
{ "enforceOnLogin", true }
|
|
}
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}/vnext",
|
|
JsonContent.Create(request));
|
|
|
|
// Assert
|
|
await AssertResponse();
|
|
|
|
await AssertPolicyDataForMasterPasswordPolicy();
|
|
return;
|
|
|
|
async Task AssertPolicyDataForMasterPasswordPolicy()
|
|
{
|
|
var policyRepository = _factory.GetService<IPolicyRepository>();
|
|
var policy = await policyRepository.GetByOrganizationIdTypeAsync(_organization.Id, policyType);
|
|
|
|
AssertPolicy(policy);
|
|
AssertMasterPasswordPolicyData(policy);
|
|
}
|
|
|
|
async Task AssertResponse()
|
|
{
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var content = await response.Content.ReadFromJsonAsync<PolicyResponseModel>();
|
|
|
|
Assert.True(content.Enabled);
|
|
Assert.Equal(policyType, content.Type);
|
|
Assert.Equal(_organization.Id, content.OrganizationId);
|
|
}
|
|
|
|
void AssertPolicy(Policy policy)
|
|
{
|
|
Assert.NotNull(policy);
|
|
Assert.True(policy.Enabled);
|
|
Assert.Equal(policyType, policy.Type);
|
|
Assert.Equal(_organization.Id, policy.OrganizationId);
|
|
Assert.NotNull(policy.Data);
|
|
}
|
|
|
|
void AssertMasterPasswordPolicyData(Policy policy)
|
|
{
|
|
var resultData = policy.GetDataModel<MasterPasswordPolicyData>();
|
|
|
|
var json = JsonSerializer.Serialize(request.Policy.Data);
|
|
var expectedData = JsonSerializer.Deserialize<MasterPasswordPolicyData>(json);
|
|
AssertHelper.AssertPropertyEqual(resultData, expectedData);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Put_MasterPasswordPolicy_InvalidDataType_ReturnsBadRequest()
|
|
{
|
|
// Arrange
|
|
var policyType = PolicyType.MasterPassword;
|
|
var request = new PolicyRequestModel
|
|
{
|
|
Enabled = true,
|
|
Data = new Dictionary<string, object>
|
|
{
|
|
{ "minLength", "not a number" }, // Wrong type - should be int
|
|
{ "requireUpper", true }
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}",
|
|
JsonContent.Create(request));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
Assert.Contains("minLength", content); // Verify field name is in error message
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Put_SendOptionsPolicy_InvalidDataType_ReturnsBadRequest()
|
|
{
|
|
// Arrange
|
|
var policyType = PolicyType.SendOptions;
|
|
var request = new PolicyRequestModel
|
|
{
|
|
Enabled = true,
|
|
Data = new Dictionary<string, object>
|
|
{
|
|
{ "disableHideEmail", "not a boolean" } // Wrong type - should be bool
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}",
|
|
JsonContent.Create(request));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Put_ResetPasswordPolicy_InvalidDataType_ReturnsBadRequest()
|
|
{
|
|
// Arrange
|
|
var policyType = PolicyType.ResetPassword;
|
|
var request = new PolicyRequestModel
|
|
{
|
|
Enabled = true,
|
|
Data = new Dictionary<string, object>
|
|
{
|
|
{ "autoEnrollEnabled", 123 } // Wrong type - should be bool
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}",
|
|
JsonContent.Create(request));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PutVNext_MasterPasswordPolicy_InvalidDataType_ReturnsBadRequest()
|
|
{
|
|
// Arrange
|
|
var policyType = PolicyType.MasterPassword;
|
|
var request = new SavePolicyRequest
|
|
{
|
|
Policy = new PolicyRequestModel
|
|
{
|
|
Enabled = true,
|
|
Data = new Dictionary<string, object>
|
|
{
|
|
{ "minComplexity", "not a number" }, // Wrong type - should be int
|
|
{ "minLength", 12 }
|
|
}
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}/vnext",
|
|
JsonContent.Create(request));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
Assert.Contains("minComplexity", content); // Verify field name is in error message
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PutVNext_SendOptionsPolicy_InvalidDataType_ReturnsBadRequest()
|
|
{
|
|
// Arrange
|
|
var policyType = PolicyType.SendOptions;
|
|
var request = new SavePolicyRequest
|
|
{
|
|
Policy = new PolicyRequestModel
|
|
{
|
|
Enabled = true,
|
|
Data = new Dictionary<string, object>
|
|
{
|
|
{ "disableHideEmail", "not a boolean" } // Wrong type - should be bool
|
|
}
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}/vnext",
|
|
JsonContent.Create(request));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PutVNext_ResetPasswordPolicy_InvalidDataType_ReturnsBadRequest()
|
|
{
|
|
// Arrange
|
|
var policyType = PolicyType.ResetPassword;
|
|
var request = new SavePolicyRequest
|
|
{
|
|
Policy = new PolicyRequestModel
|
|
{
|
|
Enabled = true,
|
|
Data = new Dictionary<string, object>
|
|
{
|
|
{ "autoEnrollEnabled", 123 } // Wrong type - should be bool
|
|
}
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}/vnext",
|
|
JsonContent.Create(request));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Put_PolicyWithNullData_Success()
|
|
{
|
|
// Arrange
|
|
var policyType = PolicyType.SingleOrg;
|
|
var request = new PolicyRequestModel
|
|
{
|
|
Enabled = true,
|
|
Data = null
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}",
|
|
JsonContent.Create(request));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PutVNext_PolicyWithNullData_Success()
|
|
{
|
|
// Arrange
|
|
var policyType = PolicyType.TwoFactorAuthentication;
|
|
var request = new SavePolicyRequest
|
|
{
|
|
Policy = new PolicyRequestModel
|
|
{
|
|
Enabled = true,
|
|
Data = null
|
|
},
|
|
Metadata = null
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}/vnext",
|
|
JsonContent.Create(request));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Put_MasterPasswordPolicy_ExcessiveMinLength_ReturnsBadRequest()
|
|
{
|
|
// Arrange
|
|
var policyType = PolicyType.MasterPassword;
|
|
var request = new PolicyRequestModel
|
|
{
|
|
Enabled = true,
|
|
Data = new Dictionary<string, object>
|
|
{
|
|
{ "minLength", 129 }
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}",
|
|
JsonContent.Create(request));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Put_MasterPasswordPolicy_ExcessiveMinComplexity_ReturnsBadRequest()
|
|
{
|
|
// Arrange
|
|
var policyType = PolicyType.MasterPassword;
|
|
var request = new PolicyRequestModel
|
|
{
|
|
Enabled = true,
|
|
Data = new Dictionary<string, object>
|
|
{
|
|
{ "minComplexity", 5 }
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}",
|
|
JsonContent.Create(request));
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
}
|
|
}
|