diff --git a/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs b/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs index 50c2af4ab9..04667e61ad 100644 --- a/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs +++ b/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs @@ -84,6 +84,19 @@ public class OrganizationSponsorshipsController : Controller throw new BadRequestException("Free Bitwarden Families sponsorship has been disabled by your organization administrator."); } + if (!_featureService.IsEnabled(Bit.Core.FeatureFlagKeys.PM17772_AdminInitiatedSponsorships)) + { + if (model.SponsoringUserId.HasValue) + { + throw new NotFoundException(); + } + + if (!string.IsNullOrWhiteSpace(model.Notes)) + { + model.Notes = null; + } + } + var targetUser = model.SponsoringUserId ?? _currentContext.UserId!.Value; var sponsorship = await _createSponsorshipCommand.CreateSponsorshipAsync( sponsoringOrg, diff --git a/src/Api/Controllers/SelfHosted/SelfHostedOrganizationSponsorshipsController.cs b/src/Api/Controllers/SelfHosted/SelfHostedOrganizationSponsorshipsController.cs index 654860c24a..6b4785fa3e 100644 --- a/src/Api/Controllers/SelfHosted/SelfHostedOrganizationSponsorshipsController.cs +++ b/src/Api/Controllers/SelfHosted/SelfHostedOrganizationSponsorshipsController.cs @@ -3,6 +3,7 @@ using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.Repositories; +using Bit.Core.Services; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -20,6 +21,7 @@ public class SelfHostedOrganizationSponsorshipsController : Controller private readonly ICreateSponsorshipCommand _offerSponsorshipCommand; private readonly IRevokeSponsorshipCommand _revokeSponsorshipCommand; private readonly ICurrentContext _currentContext; + private readonly IFeatureService _featureService; public SelfHostedOrganizationSponsorshipsController( ICreateSponsorshipCommand offerSponsorshipCommand, @@ -27,7 +29,8 @@ public class SelfHostedOrganizationSponsorshipsController : Controller IOrganizationRepository organizationRepository, IOrganizationSponsorshipRepository organizationSponsorshipRepository, IOrganizationUserRepository organizationUserRepository, - ICurrentContext currentContext + ICurrentContext currentContext, + IFeatureService featureService ) { _offerSponsorshipCommand = offerSponsorshipCommand; @@ -36,14 +39,31 @@ public class SelfHostedOrganizationSponsorshipsController : Controller _organizationSponsorshipRepository = organizationSponsorshipRepository; _organizationUserRepository = organizationUserRepository; _currentContext = currentContext; + _featureService = featureService; } [HttpPost("{sponsoringOrgId}/families-for-enterprise")] public async Task CreateSponsorship(Guid sponsoringOrgId, [FromBody] OrganizationSponsorshipCreateRequestModel model) { + // Check feature flag at controller level + if (!_featureService.IsEnabled(Bit.Core.FeatureFlagKeys.PM17772_AdminInitiatedSponsorships)) + { + // If flag is off and someone tries to use admin-initiated sponsorships, return 404 + if (model.SponsoringUserId.HasValue) + { + throw new NotFoundException(); + } + + // If flag is off and notes field has a value, ignore it + if (!string.IsNullOrWhiteSpace(model.Notes)) + { + model.Notes = null; + } + } + await _offerSponsorshipCommand.CreateSponsorshipAsync( await _organizationRepository.GetByIdAsync(sponsoringOrgId), - await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default), + await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, model.SponsoringUserId ?? _currentContext.UserId ?? default), model.PlanSponsorshipType, model.SponsoredEmail, model.FriendlyName, model.Notes); } diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs index 644e25499e..27589bea3e 100644 --- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs @@ -13,7 +13,6 @@ namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnte public class CreateSponsorshipCommand( ICurrentContext currentContext, - IFeatureService featureService, IOrganizationSponsorshipRepository organizationSponsorshipRepository, IUserService userService) : ICreateSponsorshipCommand { @@ -65,11 +64,6 @@ public class CreateSponsorshipCommand( var isAdminInitiated = false; if (currentContext.UserId != sponsoringMember.UserId) { - if (!featureService.IsEnabled(FeatureFlagKeys.PM17772_AdminInitiatedSponsorships)) - { - throw new BadRequestException("Feature 'pm-17772-admin-initiated-sponsorships' is not enabled."); - } - var organization = currentContext.Organizations.First(x => x.Id == sponsoringOrganization.Id); OrganizationUserType[] allowedUserTypes = [ diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommandTests.cs index 1e4f042874..93a2b629f0 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommandTests.cs @@ -225,10 +225,6 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase sponsoringOrg.PlanType = PlanType.EnterpriseAnnually; sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed; - sutProvider.GetDependency() - .IsEnabled(Arg.Is(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships)) - .Returns(true); - sutProvider.GetDependency().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user); sutProvider.GetDependency().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo => { @@ -248,7 +244,7 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase var actual = await Assert.ThrowsAsync(async () => await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser, - PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, null)); + PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, notes: null)); Assert.Equal("You do not have permissions to send sponsorships on behalf of the organization.", actual.Message); } @@ -264,10 +260,6 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase sponsoringOrg.PlanType = PlanType.EnterpriseAnnually; sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed; - sutProvider.GetDependency() - .IsEnabled(Arg.Is(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships)) - .Returns(true); - sutProvider.GetDependency().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user); sutProvider.GetDependency().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo => { @@ -279,14 +271,14 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase new() { Id = sponsoringOrg.Id, + Permissions = new Permissions(), Type = organizationUserType } ]); - var actual = await Assert.ThrowsAsync(async () => await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser, - PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, null)); + PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, notes: null)); Assert.Equal("You do not have permissions to send sponsorships on behalf of the organization.", actual.Message); } @@ -300,12 +292,9 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase string friendlyName, Guid sponsorshipId, Guid currentUserId, string notes, SutProvider sutProvider) { sponsoringOrg.PlanType = PlanType.EnterpriseAnnually; + sponsoringOrg.UseAdminSponsoredFamilies = true; sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed; - sutProvider.GetDependency() - .IsEnabled(Arg.Is(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships)) - .Returns(true); - sutProvider.GetDependency().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user); sutProvider.GetDependency().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo => { @@ -317,6 +306,7 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase new() { Id = sponsoringOrg.Id, + Permissions = new Permissions { ManageUsers = true }, Type = organizationUserType } ]);