diff --git a/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs b/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs index 27da5cc561..41f49b0bd1 100644 --- a/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs +++ b/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs @@ -1,8 +1,9 @@ using System.ComponentModel.DataAnnotations; -using System.Text.Json; using Bit.Api.Models.Public.Response; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; +using Newtonsoft.Json; +using JsonSerializer = System.Text.Json.JsonSerializer; namespace Bit.Api.AdminConsole.Public.Models.Response; @@ -11,6 +12,9 @@ namespace Bit.Api.AdminConsole.Public.Models.Response; /// public class PolicyResponseModel : PolicyBaseModel, IResponseModel { + [JsonConstructor] + public PolicyResponseModel() {} + public PolicyResponseModel(Policy policy) { if (policy == null) diff --git a/test/Api.IntegrationTest/AdminConsole/Public/Controllers/PoliciesControllerTests.cs b/test/Api.IntegrationTest/AdminConsole/Public/Controllers/PoliciesControllerTests.cs new file mode 100644 index 0000000000..f549f0297b --- /dev/null +++ b/test/Api.IntegrationTest/AdminConsole/Public/Controllers/PoliciesControllerTests.cs @@ -0,0 +1,166 @@ +using System.Net; +using System.Text.Json; +using Bit.Api.AdminConsole.Public.Models; +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.Api.Models.Public.Response; +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.Models.Data; +using Bit.Core.Repositories; +using Bit.Core.Utilities; +using Bit.Test.Common.Helpers; +using Xunit; + +namespace Bit.Api.IntegrationTest.AdminConsole.Public.Controllers; + +public class PoliciesControllerTests : IClassFixture, 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 + { + { "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(); + Assert.NotNull(result); + + Assert.True(result.Enabled); + Assert.Equal(policyType, result.Type); + Assert.IsType(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(); + var policy = await policyRepository.GetByOrganizationIdTypeAsync(_organization.Id, policyType); + Assert.NotNull(policy); + + Assert.True(policy.Enabled); + Assert.Equal(policyType, policy.Type); + Assert.IsType(policy.Id); + Assert.NotEqual(default, policy.Id); + Assert.Equal(_organization.Id, policy.OrganizationId); + + Assert.NotNull(policy.Data); + var data = policy.GetDataModel(); + 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(); + 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 + { + { "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(); + 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(); + Assert.Equal(15, data.MinLength); + Assert.Equal(true, data.RequireUpper); + } +} diff --git a/test/Api.Test/AdminConsole/Public/Controllers/PoliciesControllerTests.cs b/test/Api.Test/AdminConsole/Public/Controllers/PoliciesControllerTests.cs deleted file mode 100644 index 71d04cae33..0000000000 --- a/test/Api.Test/AdminConsole/Public/Controllers/PoliciesControllerTests.cs +++ /dev/null @@ -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 sutProvider) - { - sutProvider.GetDependency().OrganizationId.Returns(organization.Id); - sutProvider.GetDependency().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); - } -}