diff --git a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModel.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModel.cs index 6373da9902cf..af3e52a5f0b4 100644 --- a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModel.cs @@ -33,6 +33,7 @@ public class OrganizationCreateRequestModel : IValidatableObject [Required] public string Key { get; set; } + [Required] public OrganizationKeysRequestModel Keys { get; set; } public PaymentMethodType? PaymentMethodType { get; set; } public string PaymentToken { get; set; } @@ -117,7 +118,7 @@ public virtual OrganizationSignup ToOrganizationSignup(User user) InitiationPath = InitiationPath, SkipTrial = SkipTrial, Coupons = Coupons, - Keys = Keys?.ToPublicKeyEncryptionKeyPairData() + Keys = Keys.ToPublicKeyEncryptionKeyPairData() }; return orgSignup; diff --git a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationNoPaymentCreateRequest.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationNoPaymentCreateRequest.cs index 81d7c413ebb0..47d04a0ee62e 100644 --- a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationNoPaymentCreateRequest.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationNoPaymentCreateRequest.cs @@ -32,6 +32,7 @@ public class OrganizationNoPaymentCreateRequest [Required] public string Key { get; set; } + [Required] public OrganizationKeysRequestModel Keys { get; set; } public PaymentMethodType? PaymentMethodType { get; set; } public string PaymentToken { get; set; } @@ -110,7 +111,7 @@ public virtual OrganizationSignup ToOrganizationSignup(User user) BillingAddressCountry = BillingAddressCountry, }, InitiationPath = InitiationPath, - Keys = Keys?.ToPublicKeyEncryptionKeyPairData() + Keys = Keys.ToPublicKeyEncryptionKeyPairData() }; return orgSignup; diff --git a/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationsControllerPerformanceTests.cs b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationsControllerPerformanceTests.cs index 1bea3dd7203b..a9243a5476dd 100644 --- a/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationsControllerPerformanceTests.cs +++ b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationsControllerPerformanceTests.cs @@ -155,7 +155,12 @@ public async Task CreateOrganization_WithoutPayment() AdditionalServiceAccounts = 2, MaxAutoscaleSeats = 100, PremiumAccessAddon = false, - CollectionName = "2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk=" + CollectionName = "2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk=", + Keys = new OrganizationKeysRequestModel + { + PublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0OaLBJiGh5GJmX8hV/a", + EncryptedPrivateKey = "2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk=" + } }; var requestContent = new StringContent(JsonSerializer.Serialize(createRequest), Encoding.UTF8, "application/json"); diff --git a/test/Api.Test/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModelTests.cs b/test/Api.Test/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModelTests.cs new file mode 100644 index 000000000000..8220f5c499e1 --- /dev/null +++ b/test/Api.Test/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModelTests.cs @@ -0,0 +1,82 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Api.AdminConsole.Models.Request.Organizations; +using Xunit; + +namespace Bit.Api.Test.AdminConsole.Models.Request.Organizations; + +public class OrganizationCreateRequestModelTests +{ + [Fact] + public void Validate_KeysMissing_FailsValidation() + { + var model = new OrganizationCreateRequestModel + { + Name = "Test Org", + BillingEmail = "test@example.com", + Key = "test-key", + UseSecretsManager = false, + Keys = null + }; + + var results = ValidateModel(model); + + Assert.Contains(results, r => r.MemberNames.Contains(nameof(OrganizationCreateRequestModel.Keys))); + } + + [Fact] + public void Validate_KeysPresent_PassesKeysValidation() + { + var model = new OrganizationCreateRequestModel + { + Name = "Test Org", + BillingEmail = "test@example.com", + Key = "test-key", + UseSecretsManager = false, + Keys = new OrganizationKeysRequestModel + { + PublicKey = "test-public-key", + EncryptedPrivateKey = "test-encrypted-private-key" + } + }; + + var results = ValidateModel(model); + + Assert.DoesNotContain(results, r => r.MemberNames.Contains(nameof(OrganizationCreateRequestModel.Keys))); + } + + [Fact] + public void Validate_KeysMissingPublicKey_FailsValidation() + { + var keys = new OrganizationKeysRequestModel + { + PublicKey = null, + EncryptedPrivateKey = "test-encrypted-private-key" + }; + + var results = ValidateModel(keys); + + Assert.Contains(results, r => r.MemberNames.Contains(nameof(OrganizationKeysRequestModel.PublicKey))); + } + + [Fact] + public void Validate_KeysMissingEncryptedPrivateKey_FailsValidation() + { + var keys = new OrganizationKeysRequestModel + { + PublicKey = "test-public-key", + EncryptedPrivateKey = null + }; + + var results = ValidateModel(keys); + + Assert.Contains(results, r => r.MemberNames.Contains(nameof(OrganizationKeysRequestModel.EncryptedPrivateKey))); + } + + private static List ValidateModel(object model) + { + var context = new ValidationContext(model); + var results = new List(); + Validator.TryValidateObject(model, context, results, validateAllProperties: true); + return results; + } +} diff --git a/test/Api.Test/AdminConsole/Models/Request/Organizations/OrganizationNoPaymentCreateRequestTests.cs b/test/Api.Test/AdminConsole/Models/Request/Organizations/OrganizationNoPaymentCreateRequestTests.cs new file mode 100644 index 000000000000..987aa66920f3 --- /dev/null +++ b/test/Api.Test/AdminConsole/Models/Request/Organizations/OrganizationNoPaymentCreateRequestTests.cs @@ -0,0 +1,82 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Api.AdminConsole.Models.Request.Organizations; +using Xunit; + +namespace Bit.Api.Test.AdminConsole.Models.Request.Organizations; + +public class OrganizationNoPaymentCreateRequestTests +{ + [Fact] + public void Validate_KeysMissing_FailsValidation() + { + var model = new OrganizationNoPaymentCreateRequest + { + Name = "Test Org", + BillingEmail = "test@example.com", + Key = "test-key", + UseSecretsManager = false, + Keys = null + }; + + var results = ValidateModel(model); + + Assert.Contains(results, r => r.MemberNames.Contains(nameof(OrganizationNoPaymentCreateRequest.Keys))); + } + + [Fact] + public void Validate_KeysPresent_PassesKeysValidation() + { + var model = new OrganizationNoPaymentCreateRequest + { + Name = "Test Org", + BillingEmail = "test@example.com", + Key = "test-key", + UseSecretsManager = false, + Keys = new OrganizationKeysRequestModel + { + PublicKey = "test-public-key", + EncryptedPrivateKey = "test-encrypted-private-key" + } + }; + + var results = ValidateModel(model); + + Assert.DoesNotContain(results, r => r.MemberNames.Contains(nameof(OrganizationNoPaymentCreateRequest.Keys))); + } + + [Fact] + public void Validate_KeysMissingPublicKey_FailsValidation() + { + var keys = new OrganizationKeysRequestModel + { + PublicKey = null, + EncryptedPrivateKey = "test-encrypted-private-key" + }; + + var results = ValidateModel(keys); + + Assert.Contains(results, r => r.MemberNames.Contains(nameof(OrganizationKeysRequestModel.PublicKey))); + } + + [Fact] + public void Validate_KeysMissingEncryptedPrivateKey_FailsValidation() + { + var keys = new OrganizationKeysRequestModel + { + PublicKey = "test-public-key", + EncryptedPrivateKey = null + }; + + var results = ValidateModel(keys); + + Assert.Contains(results, r => r.MemberNames.Contains(nameof(OrganizationKeysRequestModel.EncryptedPrivateKey))); + } + + private static List ValidateModel(object model) + { + var context = new ValidationContext(model); + var results = new List(); + Validator.TryValidateObject(model, context, results, validateAllProperties: true); + return results; + } +}