Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e80c30f
Enhance PolicyRequestModel and SavePolicyRequest with validation for โ€ฆ
r-tome Oct 16, 2025
47f4c37
Add integration tests for policy updates to validate handling of invaโ€ฆ
r-tome Oct 16, 2025
06c52c0
Add missing using
r-tome Oct 16, 2025
3149755
Update PolicyRequestModel for null safety by making Data and Validateโ€ฆ
r-tome Oct 16, 2025
9950637
Merge branch 'main' into ac/pm-26429/add-validation-to-policy-data-anโ€ฆ
r-tome Oct 17, 2025
14ffd48
Add integration tests for public PoliciesController to validate handlโ€ฆ
r-tome Oct 17, 2025
47d52e3
Add PolicyDataValidator class for validating and serializing policy dโ€ฆ
r-tome Oct 17, 2025
f27766b
Refactor PolicyRequestModel, SavePolicyRequest, and PolicyUpdateRequeโ€ฆ
r-tome Oct 17, 2025
b968b9b
Update PolicyRequestModel and SavePolicyRequest to initialize Data anโ€ฆ
r-tome Oct 17, 2025
50ad9c4
Refactor PolicyDataValidator to remove null checks for input data in โ€ฆ
r-tome Oct 17, 2025
4437016
Rename test methods in SavePolicyRequestTests to reflect handling of โ€ฆ
r-tome Oct 17, 2025
02f79f6
Merge branch 'main' into ac/pm-26429/add-validation-to-policy-data-anโ€ฆ
r-tome Oct 20, 2025
2ea2b02
Merge branch 'main' into ac/pm-26429/add-validation-to-policy-data-anโ€ฆ
r-tome Oct 21, 2025
733ffe9
Merge branch 'main' into ac/pm-26429/add-validation-to-policy-data-anโ€ฆ
r-tome Oct 22, 2025
bc6ba3b
Merge branch 'main' into ac/pm-26429/add-validation-to-policy-data-anโ€ฆ
r-tome Oct 23, 2025
3aaaf0e
Enhance error handling in PolicyDataValidator to include field-specifโ€ฆ
r-tome Oct 23, 2025
4433ba9
Enhance PoliciesControllerTests to verify error messages for BadRequeโ€ฆ
r-tome Oct 23, 2025
f957c91
Merge branch 'main' into ac/pm-26429/add-validation-to-policy-data-anโ€ฆ
r-tome Oct 24, 2025
a77d60c
Merge branch 'main' into ac/pm-26429/add-validation-to-policy-data-anโ€ฆ
r-tome Oct 28, 2025
c6a8451
refactor: Update PolicyRequestModel and SavePolicyRequest to use nullโ€ฆ
r-tome Oct 28, 2025
e5e901d
test: Add integration tests for handling policies with null data in Pโ€ฆ
r-tome Oct 28, 2025
399177d
fix: Catch specific JsonException in PolicyDataValidator to improve eโ€ฆ
r-tome Oct 28, 2025
5a98d8d
test: Add unit tests for PolicyDataValidator to validate and serializโ€ฆ
r-tome Oct 28, 2025
6a31bd8
test: Update PolicyDataValidatorTests to validate organization data oโ€ฆ
r-tome Oct 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions src/Api/AdminConsole/Models/Request/PolicyRequestModel.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
๏ปฟ// FIXME: Update this file to be null safe and then delete the line below
#nullable disable

using System.ComponentModel.DataAnnotations;
using System.Text.Json;
๏ปฟ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;
using Bit.Core.Context;

namespace Bit.Api.AdminConsole.Models.Request;
Expand All @@ -16,14 +13,20 @@ public class PolicyRequestModel
public PolicyType? Type { get; set; }
[Required]
public bool? Enabled { get; set; }
public Dictionary<string, object> Data { get; set; }
public Dictionary<string, object>? Data { get; set; }

public async Task<PolicyUpdate> ToPolicyUpdateAsync(Guid organizationId, ICurrentContext currentContext) => new()
public async Task<PolicyUpdate> ToPolicyUpdateAsync(Guid organizationId, ICurrentContext currentContext)
{
Type = Type!.Value,
OrganizationId = organizationId,
Data = Data != null ? JsonSerializer.Serialize(Data) : null,
Enabled = Enabled.GetValueOrDefault(),
PerformedBy = new StandardUser(currentContext.UserId!.Value, await currentContext.OrganizationOwner(organizationId))
};
var serializedData = PolicyDataValidator.ValidateAndSerialize(Data, Type!.Value);
var performedBy = new StandardUser(currentContext.UserId!.Value, await currentContext.OrganizationOwner(organizationId));

return new()
{
Type = Type!.Value,
OrganizationId = organizationId,
Data = serializedData,
Enabled = Enabled.GetValueOrDefault(),
PerformedBy = performedBy
};
}
}
45 changes: 4 additions & 41 deletions src/Api/AdminConsole/Models/Request/SavePolicyRequest.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
๏ปฟusing System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.Utilities;
using Bit.Core.Context;
using Bit.Core.Utilities;

namespace Bit.Api.AdminConsole.Models.Request;

Expand All @@ -17,45 +15,10 @@ public class SavePolicyRequest

public async Task<SavePolicyModel> ToSavePolicyModelAsync(Guid organizationId, ICurrentContext currentContext)
{
var policyUpdate = await Policy.ToPolicyUpdateAsync(organizationId, currentContext);
var metadata = PolicyDataValidator.ValidateAndDeserializeMetadata(Metadata, Policy.Type!.Value);
var performedBy = new StandardUser(currentContext.UserId!.Value, await currentContext.OrganizationOwner(organizationId));

var updatedPolicy = new PolicyUpdate()
{
Type = Policy.Type!.Value,
OrganizationId = organizationId,
Data = Policy.Data != null ? JsonSerializer.Serialize(Policy.Data) : null,
Enabled = Policy.Enabled.GetValueOrDefault(),
};

var metadata = MapToPolicyMetadata();

return new SavePolicyModel(updatedPolicy, performedBy, metadata);
}

private IPolicyMetadataModel MapToPolicyMetadata()
{
if (Metadata == null)
{
return new EmptyMetadataModel();
}

return Policy?.Type switch
{
PolicyType.OrganizationDataOwnership => MapToPolicyMetadata<OrganizationModelOwnershipPolicyModel>(),
_ => new EmptyMetadataModel()
};
}

private IPolicyMetadataModel MapToPolicyMetadata<T>() where T : IPolicyMetadataModel, new()
{
try
{
var json = JsonSerializer.Serialize(Metadata);
return CoreHelpers.LoadClassFromJsonData<T>(json);
}
catch
{
return new EmptyMetadataModel();
}
return new SavePolicyModel(policyUpdate, performedBy, metadata);
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
๏ปฟusing System.Text.Json;
using Bit.Core.AdminConsole.Enums;
๏ปฟusing Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.Utilities;
using Bit.Core.Enums;

namespace Bit.Api.AdminConsole.Public.Models.Request;

public class PolicyUpdateRequestModel : PolicyBaseModel
{
public PolicyUpdate ToPolicyUpdate(Guid organizationId, PolicyType type) => new()
public PolicyUpdate ToPolicyUpdate(Guid organizationId, PolicyType type)
{
Type = type,
OrganizationId = organizationId,
Data = Data != null ? JsonSerializer.Serialize(Data) : null,
Enabled = Enabled.GetValueOrDefault(),
PerformedBy = new SystemUser(EventSystemUser.PublicApi)
};
var serializedData = PolicyDataValidator.ValidateAndSerialize(Data, type);

return new()
{
Type = type,
OrganizationId = organizationId,
Data = serializedData,
Enabled = Enabled.GetValueOrDefault(),
PerformedBy = new SystemUser(EventSystemUser.PublicApi)
};
}
}
81 changes: 81 additions & 0 deletions src/Core/AdminConsole/Utilities/PolicyDataValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
๏ปฟusing System.Text.Json;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;

namespace Bit.Core.AdminConsole.Utilities;

public static class PolicyDataValidator
{
/// <summary>
/// Validates and serializes policy data based on the policy type.
/// </summary>
/// <param name="data">The policy data to validate</param>
/// <param name="policyType">The type of policy</param>
/// <returns>Serialized JSON string if data is valid, null if data is null or empty</returns>
/// <exception cref="BadRequestException">Thrown when data validation fails</exception>
public static string? ValidateAndSerialize(Dictionary<string, object>? data, PolicyType policyType)
{
if (data == null || data.Count == 0)
{
return null;
}

try
{
var json = JsonSerializer.Serialize(data);

switch (policyType)
{
case PolicyType.MasterPassword:
CoreHelpers.LoadClassFromJsonData<MasterPasswordPolicyData>(json);
break;
case PolicyType.SendOptions:
CoreHelpers.LoadClassFromJsonData<SendOptionsPolicyData>(json);
break;
case PolicyType.ResetPassword:
CoreHelpers.LoadClassFromJsonData<ResetPasswordDataModel>(json);
break;
}

return json;
}
catch (JsonException ex)
{
var fieldInfo = !string.IsNullOrEmpty(ex.Path) ? $": field '{ex.Path}' has invalid type" : "";
throw new BadRequestException($"Invalid data for {policyType} policy{fieldInfo}.");
}
}

/// <summary>
/// Validates and deserializes policy metadata based on the policy type.
/// </summary>
/// <param name="metadata">The policy metadata to validate</param>
/// <param name="policyType">The type of policy</param>
/// <returns>Deserialized metadata model, or EmptyMetadataModel if metadata is null, empty, or validation fails</returns>
public static IPolicyMetadataModel ValidateAndDeserializeMetadata(Dictionary<string, object>? metadata, PolicyType policyType)
{
if (metadata == null || metadata.Count == 0)
{
return new EmptyMetadataModel();
}

try
{
var json = JsonSerializer.Serialize(metadata);

return policyType switch
{
PolicyType.OrganizationDataOwnership =>
CoreHelpers.LoadClassFromJsonData<OrganizationModelOwnershipPolicyModel>(json),
_ => new EmptyMetadataModel()
};
}
catch (JsonException)
{
return new EmptyMetadataModel();
}
}
}
Loading
Loading