From 7d39efe29ff122fc9c411bb504cf7ffe34386f55 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 10 Nov 2025 08:40:40 +0100 Subject: [PATCH 1/2] [PM-27575] Add support for loading Mailer templates from disk (#6520) Adds support for overloading mail templates from disk. --- .../Mail/Mailer/HandlebarMailRenderer.cs | 59 ++++++- .../Mailer/HandlebarMailRendererTests.cs | 154 +++++++++++++++++- test/Core.Test/Platform/Mailer/MailerTest.cs | 8 +- 3 files changed, 216 insertions(+), 5 deletions(-) diff --git a/src/Core/Platform/Mail/Mailer/HandlebarMailRenderer.cs b/src/Core/Platform/Mail/Mailer/HandlebarMailRenderer.cs index 608d6d6be0..baba5b8015 100644 --- a/src/Core/Platform/Mail/Mailer/HandlebarMailRenderer.cs +++ b/src/Core/Platform/Mail/Mailer/HandlebarMailRenderer.cs @@ -1,7 +1,9 @@ #nullable enable using System.Collections.Concurrent; using System.Reflection; +using Bit.Core.Settings; using HandlebarsDotNet; +using Microsoft.Extensions.Logging; namespace Bit.Core.Platform.Mail.Mailer; public class HandlebarMailRenderer : IMailRenderer @@ -9,7 +11,7 @@ public class HandlebarMailRenderer : IMailRenderer /// /// Lazy-initialized Handlebars instance. Thread-safe and ensures initialization occurs only once. /// - private readonly Lazy> _handlebarsTask = new(InitializeHandlebarsAsync, LazyThreadSafetyMode.ExecutionAndPublication); + private readonly Lazy> _handlebarsTask; /// /// Helper function that returns the handlebar instance. @@ -21,6 +23,17 @@ public class HandlebarMailRenderer : IMailRenderer /// private readonly ConcurrentDictionary>>> _templateCache = new(); + private readonly ILogger _logger; + private readonly GlobalSettings _globalSettings; + + public HandlebarMailRenderer(ILogger logger, GlobalSettings globalSettings) + { + _logger = logger; + _globalSettings = globalSettings; + + _handlebarsTask = new Lazy>(InitializeHandlebarsAsync, LazyThreadSafetyMode.ExecutionAndPublication); + } + public async Task<(string html, string txt)> RenderAsync(BaseMailView model) { var html = await CompileTemplateAsync(model, "html"); @@ -53,19 +66,59 @@ public class HandlebarMailRenderer : IMailRenderer return handlebars.Compile(source); } - private static async Task ReadSourceAsync(Assembly assembly, string template) + private async Task ReadSourceAsync(Assembly assembly, string template) { if (assembly.GetManifestResourceNames().All(f => f != template)) { throw new FileNotFoundException("Template not found: " + template); } + var diskSource = await ReadSourceFromDiskAsync(template); + if (!string.IsNullOrWhiteSpace(diskSource)) + { + return diskSource; + } + await using var s = assembly.GetManifestResourceStream(template)!; using var sr = new StreamReader(s); return await sr.ReadToEndAsync(); } - private static async Task InitializeHandlebarsAsync() + private async Task ReadSourceFromDiskAsync(string template) + { + if (!_globalSettings.SelfHosted) + { + return null; + } + + try + { + var diskPath = Path.GetFullPath(Path.Combine(_globalSettings.MailTemplateDirectory, template)); + var baseDirectory = Path.GetFullPath(_globalSettings.MailTemplateDirectory); + + // Ensure the resolved path is within the configured directory + if (!diskPath.StartsWith(baseDirectory + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) && + !diskPath.Equals(baseDirectory, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogWarning("Template path traversal attempt detected: {Template}", template); + return null; + } + + if (File.Exists(diskPath)) + { + var fileContents = await File.ReadAllTextAsync(diskPath); + return fileContents; + } + } + catch (Exception e) + { + _logger.LogError(e, "Failed to read mail template from disk: {TemplateName}", template); + } + + return null; + } + + private async Task InitializeHandlebarsAsync() { var handlebars = Handlebars.Create(); diff --git a/test/Core.Test/Platform/Mailer/HandlebarMailRendererTests.cs b/test/Core.Test/Platform/Mailer/HandlebarMailRendererTests.cs index 1cc7504702..2559ae2b5f 100644 --- a/test/Core.Test/Platform/Mailer/HandlebarMailRendererTests.cs +++ b/test/Core.Test/Platform/Mailer/HandlebarMailRendererTests.cs @@ -1,5 +1,8 @@ using Bit.Core.Platform.Mail.Mailer; +using Bit.Core.Settings; using Bit.Core.Test.Platform.Mailer.TestMail; +using Microsoft.Extensions.Logging; +using NSubstitute; using Xunit; namespace Bit.Core.Test.Platform.Mailer; @@ -9,7 +12,10 @@ public class HandlebarMailRendererTests [Fact] public async Task RenderAsync_ReturnsExpectedHtmlAndTxt() { - var renderer = new HandlebarMailRenderer(); + var logger = Substitute.For>(); + var globalSettings = new GlobalSettings { SelfHosted = false }; + var renderer = new HandlebarMailRenderer(logger, globalSettings); + var view = new TestMailView { Name = "John Smith" }; var (html, txt) = await renderer.RenderAsync(view); @@ -17,4 +23,150 @@ public class HandlebarMailRendererTests Assert.Equal("Hello John Smith", html.Trim()); Assert.Equal("Hello John Smith", txt.Trim()); } + + [Fact] + public async Task RenderAsync_LoadsFromDisk_WhenSelfHostedAndFileExists() + { + var logger = Substitute.For>(); + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + try + { + var globalSettings = new GlobalSettings + { + SelfHosted = true, + MailTemplateDirectory = tempDir + }; + + // Create test template files on disk + var htmlTemplatePath = Path.Combine(tempDir, "Bit.Core.Test.Platform.Mailer.TestMail.TestMailView.html.hbs"); + var txtTemplatePath = Path.Combine(tempDir, "Bit.Core.Test.Platform.Mailer.TestMail.TestMailView.text.hbs"); + await File.WriteAllTextAsync(htmlTemplatePath, "Custom HTML: {{Name}}"); + await File.WriteAllTextAsync(txtTemplatePath, "Custom TXT: {{Name}}"); + + var renderer = new HandlebarMailRenderer(logger, globalSettings); + var view = new TestMailView { Name = "Jane Doe" }; + + var (html, txt) = await renderer.RenderAsync(view); + + Assert.Equal("Custom HTML: Jane Doe", html.Trim()); + Assert.Equal("Custom TXT: Jane Doe", txt.Trim()); + } + finally + { + // Cleanup + if (Directory.Exists(tempDir)) + { + Directory.Delete(tempDir, true); + } + } + } + + [Theory] + [InlineData("../../../etc/passwd")] + [InlineData("../../../../malicious.txt")] + [InlineData("../../malicious.txt")] + [InlineData("../malicious.txt")] + public async Task ReadSourceFromDiskAsync_PrevenetsPathTraversal_WhenMaliciousPathProvided(string maliciousPath) + { + var logger = Substitute.For>(); + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + try + { + var globalSettings = new GlobalSettings + { + SelfHosted = true, + MailTemplateDirectory = tempDir + }; + + // Create a malicious file outside the template directory + var maliciousFile = Path.Combine(Path.GetTempPath(), "malicious.txt"); + await File.WriteAllTextAsync(maliciousFile, "Malicious Content"); + + var renderer = new HandlebarMailRenderer(logger, globalSettings); + + // Use reflection to call the private ReadSourceFromDiskAsync method + var method = typeof(HandlebarMailRenderer).GetMethod("ReadSourceFromDiskAsync", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var task = (Task)method!.Invoke(renderer, new object[] { maliciousPath })!; + var result = await task; + + // Should return null and not load the malicious file + Assert.Null(result); + + // Verify that a warning was logged for the path traversal attempt + logger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>()); + + // Cleanup malicious file + if (File.Exists(maliciousFile)) + { + File.Delete(maliciousFile); + } + } + finally + { + // Cleanup + if (Directory.Exists(tempDir)) + { + Directory.Delete(tempDir, true); + } + } + } + + [Fact] + public async Task ReadSourceFromDiskAsync_AllowsValidFileWithDifferentCase_WhenCaseInsensitiveFileSystem() + { + var logger = Substitute.For>(); + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + try + { + var globalSettings = new GlobalSettings + { + SelfHosted = true, + MailTemplateDirectory = tempDir + }; + + // Create a test template file + var templateFileName = "TestTemplate.hbs"; + var templatePath = Path.Combine(tempDir, templateFileName); + await File.WriteAllTextAsync(templatePath, "Test Content"); + + var renderer = new HandlebarMailRenderer(logger, globalSettings); + + // Try to read with different case (should work on case-insensitive file systems like Windows/macOS) + var method = typeof(HandlebarMailRenderer).GetMethod("ReadSourceFromDiskAsync", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var task = (Task)method!.Invoke(renderer, new object[] { templateFileName })!; + var result = await task; + + // Should successfully read the file + Assert.Equal("Test Content", result); + + // Verify no warning was logged + logger.DidNotReceive().Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>()); + } + finally + { + // Cleanup + if (Directory.Exists(tempDir)) + { + Directory.Delete(tempDir, true); + } + } + } } diff --git a/test/Core.Test/Platform/Mailer/MailerTest.cs b/test/Core.Test/Platform/Mailer/MailerTest.cs index adaf458de0..ca9cb2a874 100644 --- a/test/Core.Test/Platform/Mailer/MailerTest.cs +++ b/test/Core.Test/Platform/Mailer/MailerTest.cs @@ -1,18 +1,24 @@ using Bit.Core.Models.Mail; using Bit.Core.Platform.Mail.Delivery; using Bit.Core.Platform.Mail.Mailer; +using Bit.Core.Settings; using Bit.Core.Test.Platform.Mailer.TestMail; +using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; namespace Bit.Core.Test.Platform.Mailer; + public class MailerTest { [Fact] public async Task SendEmailAsync() { + var logger = Substitute.For>(); + var globalSettings = new GlobalSettings { SelfHosted = false }; var deliveryService = Substitute.For(); - var mailer = new Core.Platform.Mail.Mailer.Mailer(new HandlebarMailRenderer(), deliveryService); + + var mailer = new Core.Platform.Mail.Mailer.Mailer(new HandlebarMailRenderer(logger, globalSettings), deliveryService); var mail = new TestMail.TestMail() { From e7f3b6b12f67c08fc270c4f2608a9db875f5181f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:27:44 +0000 Subject: [PATCH 2/2] [PM-26430] Remove Type property from PolicyRequestModel to use route parameter only (#6472) * Enhance PolicyRequestModel and SavePolicyRequest with validation for policy data and metadata. * Add integration tests for policy updates to validate handling of invalid data types in PolicyRequestModel and SavePolicyRequest. * Add missing using * Update PolicyRequestModel for null safety by making Data and ValidateAndSerializePolicyData nullable * Add integration tests for public PoliciesController to validate handling of invalid data types in policy updates. * Add PolicyDataValidator class for validating and serializing policy data and metadata based on policy type. * Refactor PolicyRequestModel, SavePolicyRequest, and PolicyUpdateRequestModel to utilize PolicyDataValidator for data validation and serialization, removing redundant methods and improving code clarity. * Update PolicyRequestModel and SavePolicyRequest to initialize Data and Metadata properties with empty dictionaries. * Refactor PolicyDataValidator to remove null checks for input data in validation methods * Rename test methods in SavePolicyRequestTests to reflect handling of empty data and metadata, and remove null assignments in test cases for improved clarity. * Remove Type property from PolicyRequestModel to use route parameter only * Run dotnet format * Enhance error handling in PolicyDataValidator to include field-specific details in BadRequestException messages. * Enhance PoliciesControllerTests to verify error messages for BadRequest responses by checking for specific field names in the response content. * refactor: Update PolicyRequestModel and SavePolicyRequest to use nullable dictionaries for Data and Metadata properties; enhance validation methods in PolicyDataValidator to handle null cases. * test: Add integration tests for handling policies with null data in PoliciesController * fix: Catch specific JsonException in PolicyDataValidator to improve error handling * test: Add unit tests for PolicyDataValidator to validate and serialize policy data and metadata * test: Remove PolicyType from PolicyRequestModel in PoliciesControllerTests * test: Update PolicyDataValidatorTests to validate organization data ownership metadata * Refactor PoliciesControllerTests to include policy type in PutVNext method calls --- .../Controllers/PoliciesController.cs | 13 ++----- .../Models/Request/PolicyRequestModel.cs | 8 ++--- .../Models/Request/SavePolicyRequest.cs | 7 ++-- .../Controllers/PoliciesControllerTests.cs | 10 ------ .../Models/Request/SavePolicyRequestTests.cs | 34 +++++++++---------- .../Controllers/PoliciesControllerTests.cs | 8 ++--- 6 files changed, 30 insertions(+), 50 deletions(-) diff --git a/src/Api/AdminConsole/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs index 1ee6dedf89..a5272413e2 100644 --- a/src/Api/AdminConsole/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs @@ -209,23 +209,17 @@ public class PoliciesController : Controller throw new NotFoundException(); } - if (type != model.Type) - { - throw new BadRequestException("Mismatched policy type"); - } - - var policyUpdate = await model.ToPolicyUpdateAsync(orgId, _currentContext); + var policyUpdate = await model.ToPolicyUpdateAsync(orgId, type, _currentContext); var policy = await _savePolicyCommand.SaveAsync(policyUpdate); return new PolicyResponseModel(policy); } - [HttpPut("{type}/vnext")] [RequireFeatureAttribute(FeatureFlagKeys.CreateDefaultLocation)] [Authorize] - public async Task PutVNext(Guid orgId, [FromBody] SavePolicyRequest model) + public async Task PutVNext(Guid orgId, PolicyType type, [FromBody] SavePolicyRequest model) { - var savePolicyRequest = await model.ToSavePolicyModelAsync(orgId, _currentContext); + var savePolicyRequest = await model.ToSavePolicyModelAsync(orgId, type, _currentContext); var policy = _featureService.IsEnabled(FeatureFlagKeys.PolicyValidatorsRefactor) ? await _vNextSavePolicyCommand.SaveAsync(savePolicyRequest) : @@ -233,5 +227,4 @@ public class PoliciesController : Controller return new PolicyResponseModel(policy); } - } diff --git a/src/Api/AdminConsole/Models/Request/PolicyRequestModel.cs b/src/Api/AdminConsole/Models/Request/PolicyRequestModel.cs index f9b9c18993..2dc7dfa7cd 100644 --- a/src/Api/AdminConsole/Models/Request/PolicyRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/PolicyRequestModel.cs @@ -9,20 +9,18 @@ namespace Bit.Api.AdminConsole.Models.Request; public class PolicyRequestModel { - [Required] - public PolicyType? Type { get; set; } [Required] public bool? Enabled { get; set; } public Dictionary? Data { get; set; } - public async Task ToPolicyUpdateAsync(Guid organizationId, ICurrentContext currentContext) + public async Task ToPolicyUpdateAsync(Guid organizationId, PolicyType type, ICurrentContext currentContext) { - var serializedData = PolicyDataValidator.ValidateAndSerialize(Data, Type!.Value); + var serializedData = PolicyDataValidator.ValidateAndSerialize(Data, type); var performedBy = new StandardUser(currentContext.UserId!.Value, await currentContext.OrganizationOwner(organizationId)); return new() { - Type = Type!.Value, + Type = type, OrganizationId = organizationId, Data = serializedData, Enabled = Enabled.GetValueOrDefault(), diff --git a/src/Api/AdminConsole/Models/Request/SavePolicyRequest.cs b/src/Api/AdminConsole/Models/Request/SavePolicyRequest.cs index 5c1acc1c36..2e2868a78a 100644 --- a/src/Api/AdminConsole/Models/Request/SavePolicyRequest.cs +++ b/src/Api/AdminConsole/Models/Request/SavePolicyRequest.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.Utilities; @@ -13,10 +14,10 @@ public class SavePolicyRequest public Dictionary? Metadata { get; set; } - public async Task ToSavePolicyModelAsync(Guid organizationId, ICurrentContext currentContext) + public async Task ToSavePolicyModelAsync(Guid organizationId, PolicyType type, ICurrentContext currentContext) { - var policyUpdate = await Policy.ToPolicyUpdateAsync(organizationId, currentContext); - var metadata = PolicyDataValidator.ValidateAndDeserializeMetadata(Metadata, Policy.Type!.Value); + var policyUpdate = await Policy.ToPolicyUpdateAsync(organizationId, type, currentContext); + var metadata = PolicyDataValidator.ValidateAndDeserializeMetadata(Metadata, type); var performedBy = new StandardUser(currentContext.UserId!.Value, await currentContext.OrganizationOwner(organizationId)); return new SavePolicyModel(policyUpdate, performedBy, metadata); diff --git a/test/Api.IntegrationTest/AdminConsole/Controllers/PoliciesControllerTests.cs b/test/Api.IntegrationTest/AdminConsole/Controllers/PoliciesControllerTests.cs index 79c31f956d..e4098ce9a9 100644 --- a/test/Api.IntegrationTest/AdminConsole/Controllers/PoliciesControllerTests.cs +++ b/test/Api.IntegrationTest/AdminConsole/Controllers/PoliciesControllerTests.cs @@ -67,7 +67,6 @@ public class PoliciesControllerTests : IClassFixture, IAs { Policy = new PolicyRequestModel { - Type = policyType, Enabled = true, }, Metadata = new Dictionary @@ -148,7 +147,6 @@ public class PoliciesControllerTests : IClassFixture, IAs { Policy = new PolicyRequestModel { - Type = policyType, Enabled = true, Data = new Dictionary { @@ -218,7 +216,6 @@ public class PoliciesControllerTests : IClassFixture, IAs var policyType = PolicyType.MasterPassword; var request = new PolicyRequestModel { - Type = policyType, Enabled = true, Data = new Dictionary { @@ -244,7 +241,6 @@ public class PoliciesControllerTests : IClassFixture, IAs var policyType = PolicyType.SendOptions; var request = new PolicyRequestModel { - Type = policyType, Enabled = true, Data = new Dictionary { @@ -267,7 +263,6 @@ public class PoliciesControllerTests : IClassFixture, IAs var policyType = PolicyType.ResetPassword; var request = new PolicyRequestModel { - Type = policyType, Enabled = true, Data = new Dictionary { @@ -292,7 +287,6 @@ public class PoliciesControllerTests : IClassFixture, IAs { Policy = new PolicyRequestModel { - Type = policyType, Enabled = true, Data = new Dictionary { @@ -321,7 +315,6 @@ public class PoliciesControllerTests : IClassFixture, IAs { Policy = new PolicyRequestModel { - Type = policyType, Enabled = true, Data = new Dictionary { @@ -347,7 +340,6 @@ public class PoliciesControllerTests : IClassFixture, IAs { Policy = new PolicyRequestModel { - Type = policyType, Enabled = true, Data = new Dictionary { @@ -371,7 +363,6 @@ public class PoliciesControllerTests : IClassFixture, IAs var policyType = PolicyType.SingleOrg; var request = new PolicyRequestModel { - Type = policyType, Enabled = true, Data = null }; @@ -393,7 +384,6 @@ public class PoliciesControllerTests : IClassFixture, IAs { Policy = new PolicyRequestModel { - Type = policyType, Enabled = true, Data = null }, diff --git a/test/Api.Test/AdminConsole/Models/Request/SavePolicyRequestTests.cs b/test/Api.Test/AdminConsole/Models/Request/SavePolicyRequestTests.cs index 75236fd719..163d66aeb4 100644 --- a/test/Api.Test/AdminConsole/Models/Request/SavePolicyRequestTests.cs +++ b/test/Api.Test/AdminConsole/Models/Request/SavePolicyRequestTests.cs @@ -24,11 +24,11 @@ public class SavePolicyRequestTests currentContext.OrganizationOwner(organizationId).Returns(true); var testData = new Dictionary { { "test", "value" } }; + var policyType = PolicyType.TwoFactorAuthentication; var model = new SavePolicyRequest { Policy = new PolicyRequestModel { - Type = PolicyType.TwoFactorAuthentication, Enabled = true, Data = testData }, @@ -36,7 +36,7 @@ public class SavePolicyRequestTests }; // Act - var result = await model.ToSavePolicyModelAsync(organizationId, currentContext); + var result = await model.ToSavePolicyModelAsync(organizationId, policyType, currentContext); // Assert Assert.Equal(PolicyType.TwoFactorAuthentication, result.PolicyUpdate.Type); @@ -63,17 +63,17 @@ public class SavePolicyRequestTests currentContext.UserId.Returns(userId); currentContext.OrganizationOwner(organizationId).Returns(false); + var policyType = PolicyType.SingleOrg; var model = new SavePolicyRequest { Policy = new PolicyRequestModel { - Type = PolicyType.SingleOrg, Enabled = false } }; // Act - var result = await model.ToSavePolicyModelAsync(organizationId, currentContext); + var result = await model.ToSavePolicyModelAsync(organizationId, policyType, currentContext); // Assert Assert.Null(result.PolicyUpdate.Data); @@ -93,17 +93,17 @@ public class SavePolicyRequestTests currentContext.UserId.Returns(userId); currentContext.OrganizationOwner(organizationId).Returns(true); + var policyType = PolicyType.SingleOrg; var model = new SavePolicyRequest { Policy = new PolicyRequestModel { - Type = PolicyType.SingleOrg, Enabled = false } }; // Act - var result = await model.ToSavePolicyModelAsync(organizationId, currentContext); + var result = await model.ToSavePolicyModelAsync(organizationId, policyType, currentContext); // Assert Assert.Null(result.PolicyUpdate.Data); @@ -124,11 +124,11 @@ public class SavePolicyRequestTests currentContext.UserId.Returns(userId); currentContext.OrganizationOwner(organizationId).Returns(true); + var policyType = PolicyType.OrganizationDataOwnership; var model = new SavePolicyRequest { Policy = new PolicyRequestModel { - Type = PolicyType.OrganizationDataOwnership, Enabled = true }, Metadata = new Dictionary @@ -138,7 +138,7 @@ public class SavePolicyRequestTests }; // Act - var result = await model.ToSavePolicyModelAsync(organizationId, currentContext); + var result = await model.ToSavePolicyModelAsync(organizationId, policyType, currentContext); // Assert Assert.IsType(result.Metadata); @@ -156,17 +156,17 @@ public class SavePolicyRequestTests currentContext.UserId.Returns(userId); currentContext.OrganizationOwner(organizationId).Returns(true); + var policyType = PolicyType.OrganizationDataOwnership; var model = new SavePolicyRequest { Policy = new PolicyRequestModel { - Type = PolicyType.OrganizationDataOwnership, Enabled = true } }; // Act - var result = await model.ToSavePolicyModelAsync(organizationId, currentContext); + var result = await model.ToSavePolicyModelAsync(organizationId, policyType, currentContext); // Assert Assert.NotNull(result); @@ -193,12 +193,11 @@ public class SavePolicyRequestTests currentContext.UserId.Returns(userId); currentContext.OrganizationOwner(organizationId).Returns(true); - + var policyType = PolicyType.ResetPassword; var model = new SavePolicyRequest { Policy = new PolicyRequestModel { - Type = PolicyType.ResetPassword, Enabled = true, Data = _complexData }, @@ -206,7 +205,7 @@ public class SavePolicyRequestTests }; // Act - var result = await model.ToSavePolicyModelAsync(organizationId, currentContext); + var result = await model.ToSavePolicyModelAsync(organizationId, policyType, currentContext); // Assert var deserializedData = JsonSerializer.Deserialize>(result.PolicyUpdate.Data); @@ -234,11 +233,11 @@ public class SavePolicyRequestTests currentContext.UserId.Returns(userId); currentContext.OrganizationOwner(organizationId).Returns(true); + var policyType = PolicyType.MaximumVaultTimeout; var model = new SavePolicyRequest { Policy = new PolicyRequestModel { - Type = PolicyType.MaximumVaultTimeout, Enabled = true }, Metadata = new Dictionary @@ -248,7 +247,7 @@ public class SavePolicyRequestTests }; // Act - var result = await model.ToSavePolicyModelAsync(organizationId, currentContext); + var result = await model.ToSavePolicyModelAsync(organizationId, policyType, currentContext); // Assert Assert.NotNull(result); @@ -266,19 +265,18 @@ public class SavePolicyRequestTests currentContext.OrganizationOwner(organizationId).Returns(true); var errorDictionary = BuildErrorDictionary(); - + var policyType = PolicyType.OrganizationDataOwnership; var model = new SavePolicyRequest { Policy = new PolicyRequestModel { - Type = PolicyType.OrganizationDataOwnership, Enabled = true }, Metadata = errorDictionary }; // Act - var result = await model.ToSavePolicyModelAsync(organizationId, currentContext); + var result = await model.ToSavePolicyModelAsync(organizationId, policyType, currentContext); // Assert Assert.NotNull(result); diff --git a/test/Api.Test/Controllers/PoliciesControllerTests.cs b/test/Api.Test/Controllers/PoliciesControllerTests.cs index 73cdd0fe29..89d6ddefdc 100644 --- a/test/Api.Test/Controllers/PoliciesControllerTests.cs +++ b/test/Api.Test/Controllers/PoliciesControllerTests.cs @@ -487,14 +487,14 @@ public class PoliciesControllerTests .Returns(policy); // Act - var result = await sutProvider.Sut.PutVNext(orgId, model); + var result = await sutProvider.Sut.PutVNext(orgId, policy.Type, model); // Assert await sutProvider.GetDependency() .Received(1) .SaveAsync(Arg.Is( m => m.PolicyUpdate.OrganizationId == orgId && - m.PolicyUpdate.Type == model.Policy.Type && + m.PolicyUpdate.Type == policy.Type && m.PolicyUpdate.Enabled == model.Policy.Enabled && m.PerformedBy.UserId == userId && m.PerformedBy.IsOrganizationOwnerOrProvider == true)); @@ -534,14 +534,14 @@ public class PoliciesControllerTests .Returns(policy); // Act - var result = await sutProvider.Sut.PutVNext(orgId, model); + var result = await sutProvider.Sut.PutVNext(orgId, policy.Type, model); // Assert await sutProvider.GetDependency() .Received(1) .VNextSaveAsync(Arg.Is( m => m.PolicyUpdate.OrganizationId == orgId && - m.PolicyUpdate.Type == model.Policy.Type && + m.PolicyUpdate.Type == policy.Type && m.PolicyUpdate.Enabled == model.Policy.Enabled && m.PerformedBy.UserId == userId && m.PerformedBy.IsOrganizationOwnerOrProvider == true));