diff --git a/src/Api/SecretsManager/Models/Response/BaseSecretResponseModel.cs b/src/Api/SecretsManager/Models/Response/BaseSecretResponseModel.cs index 0579baec07d9..622499016a5c 100644 --- a/src/Api/SecretsManager/Models/Response/BaseSecretResponseModel.cs +++ b/src/Api/SecretsManager/Models/Response/BaseSecretResponseModel.cs @@ -1,4 +1,6 @@ -using Bit.Core.Models.Api; +#nullable enable + +using Bit.Core.Models.Api; using Bit.Core.SecretsManager.Entities; namespace Bit.Api.SecretsManager.Models.Response; @@ -24,29 +26,21 @@ public BaseSecretResponseModel(Secret secret, string objectName = _objectName) : Projects = secret.Projects?.Select(p => new SecretResponseInnerProject(p)); } - public BaseSecretResponseModel(string objectName = _objectName) : base(objectName) - { - } - - public BaseSecretResponseModel() : base(_objectName) - { - } - - public Guid Id { get; set; } + public Guid Id { get; } - public Guid OrganizationId { get; set; } + public Guid OrganizationId { get; } - public string Key { get; set; } + public string? Key { get; } - public string Value { get; set; } + public string? Value { get; } - public string Note { get; set; } + public string? Note { get; } - public DateTime CreationDate { get; set; } + public DateTime CreationDate { get; } - public DateTime RevisionDate { get; set; } + public DateTime RevisionDate { get; } - public IEnumerable Projects { get; set; } + public IEnumerable? Projects { get; init; } public class SecretResponseInnerProject { diff --git a/src/Api/SecretsManager/Models/Response/SecretResponseModel.cs b/src/Api/SecretsManager/Models/Response/SecretResponseModel.cs index c4633f78f05c..b2953ed123a0 100644 --- a/src/Api/SecretsManager/Models/Response/SecretResponseModel.cs +++ b/src/Api/SecretsManager/Models/Response/SecretResponseModel.cs @@ -12,10 +12,6 @@ public SecretResponseModel(Secret secret, bool read, bool write) : base(secret, Write = write; } - public SecretResponseModel() : base(_objectName) - { - } - public bool Read { get; set; } public bool Write { get; set; } diff --git a/src/Api/Utilities/ServiceCollectionExtensions.cs b/src/Api/Utilities/ServiceCollectionExtensions.cs index 310a7758911e..150f9c6d0dd6 100644 --- a/src/Api/Utilities/ServiceCollectionExtensions.cs +++ b/src/Api/Utilities/ServiceCollectionExtensions.cs @@ -71,6 +71,7 @@ public static void AddSwagger(this IServiceCollection services, GlobalSettings g // config.UseReferencedDefinitionsForEnums(); config.SchemaFilter(); + config.SchemaFilter(); var apiFilePath = Path.Combine(AppContext.BaseDirectory, "Api.xml"); config.IncludeXmlComments(apiFilePath, true); diff --git a/src/SharedWeb/Swagger/RequireNotNullableSchemaFilter.cs b/src/SharedWeb/Swagger/RequireNotNullableSchemaFilter.cs new file mode 100644 index 000000000000..93de13c17ad6 --- /dev/null +++ b/src/SharedWeb/Swagger/RequireNotNullableSchemaFilter.cs @@ -0,0 +1,66 @@ +using System.Reflection; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Bit.SharedWeb.Swagger; + +/// +/// +/// +/// +/// Credits: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2036#issuecomment-894015122 +/// +public class RequireNotNullableSchemaFilter : ISchemaFilter +{ + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + if (schema.Properties == null) + { + return; + } + + FixNullableProperties(schema, context); + + var notNullableProperties = schema + .Properties + .Where(x => !x.Value.Nullable && x.Value.Default == default && !schema.Required.Contains(x.Key)) + .ToList(); + + foreach (var property in notNullableProperties) + { + schema.Required.Add(property.Key); + } + } + + /// + /// Option "SupportNonNullableReferenceTypes" not working with complex types ({ "type": "object" }), + /// so they always have "Nullable = false", + /// see method "SchemaGenerator.GenerateSchemaForMember" + /// + private static void FixNullableProperties(OpenApiSchema schema, SchemaFilterContext context) + { + foreach (var property in schema.Properties) + { + var field = context.Type + .GetMembers(BindingFlags.Public | BindingFlags.Instance) + .FirstOrDefault(x => + string.Equals(x.Name, property.Key, StringComparison.InvariantCultureIgnoreCase)); + + if (field == null) + { + continue; + } + + var fieldType = field switch + { + FieldInfo fieldInfo => fieldInfo.FieldType, + PropertyInfo propertyInfo => propertyInfo.PropertyType, + _ => throw new NotSupportedException(), + }; + + property.Value.Nullable = fieldType.IsValueType + ? Nullable.GetUnderlyingType(fieldType) != null + : !field.IsNonNullableReferenceType(); + } + } +}