1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-26 07:12:20 -05:00

Properly encapsulating this PR behind its feature flag

This commit is contained in:
Conner Turnbull 2025-04-15 14:49:00 -04:00
parent ed430639b5
commit 7c0b7a4bb6
No known key found for this signature in database
4 changed files with 40 additions and 23 deletions

View File

@ -84,6 +84,19 @@ public class OrganizationSponsorshipsController : Controller
throw new BadRequestException("Free Bitwarden Families sponsorship has been disabled by your organization administrator."); 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 targetUser = model.SponsoringUserId ?? _currentContext.UserId!.Value;
var sponsorship = await _createSponsorshipCommand.CreateSponsorshipAsync( var sponsorship = await _createSponsorshipCommand.CreateSponsorshipAsync(
sponsoringOrg, sponsoringOrg,

View File

@ -3,6 +3,7 @@ using Bit.Core.Context;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -20,6 +21,7 @@ public class SelfHostedOrganizationSponsorshipsController : Controller
private readonly ICreateSponsorshipCommand _offerSponsorshipCommand; private readonly ICreateSponsorshipCommand _offerSponsorshipCommand;
private readonly IRevokeSponsorshipCommand _revokeSponsorshipCommand; private readonly IRevokeSponsorshipCommand _revokeSponsorshipCommand;
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly IFeatureService _featureService;
public SelfHostedOrganizationSponsorshipsController( public SelfHostedOrganizationSponsorshipsController(
ICreateSponsorshipCommand offerSponsorshipCommand, ICreateSponsorshipCommand offerSponsorshipCommand,
@ -27,7 +29,8 @@ public class SelfHostedOrganizationSponsorshipsController : Controller
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
IOrganizationSponsorshipRepository organizationSponsorshipRepository, IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
ICurrentContext currentContext ICurrentContext currentContext,
IFeatureService featureService
) )
{ {
_offerSponsorshipCommand = offerSponsorshipCommand; _offerSponsorshipCommand = offerSponsorshipCommand;
@ -36,14 +39,31 @@ public class SelfHostedOrganizationSponsorshipsController : Controller
_organizationSponsorshipRepository = organizationSponsorshipRepository; _organizationSponsorshipRepository = organizationSponsorshipRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_currentContext = currentContext; _currentContext = currentContext;
_featureService = featureService;
} }
[HttpPost("{sponsoringOrgId}/families-for-enterprise")] [HttpPost("{sponsoringOrgId}/families-for-enterprise")]
public async Task CreateSponsorship(Guid sponsoringOrgId, [FromBody] OrganizationSponsorshipCreateRequestModel model) 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 _offerSponsorshipCommand.CreateSponsorshipAsync(
await _organizationRepository.GetByIdAsync(sponsoringOrgId), 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); model.PlanSponsorshipType, model.SponsoredEmail, model.FriendlyName, model.Notes);
} }

View File

@ -13,7 +13,6 @@ namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnte
public class CreateSponsorshipCommand( public class CreateSponsorshipCommand(
ICurrentContext currentContext, ICurrentContext currentContext,
IFeatureService featureService,
IOrganizationSponsorshipRepository organizationSponsorshipRepository, IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IUserService userService) : ICreateSponsorshipCommand IUserService userService) : ICreateSponsorshipCommand
{ {
@ -65,11 +64,6 @@ public class CreateSponsorshipCommand(
var isAdminInitiated = false; var isAdminInitiated = false;
if (currentContext.UserId != sponsoringMember.UserId) 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); var organization = currentContext.Organizations.First(x => x.Id == sponsoringOrganization.Id);
OrganizationUserType[] allowedUserTypes = OrganizationUserType[] allowedUserTypes =
[ [

View File

@ -225,10 +225,6 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually; sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed; sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
.Returns(true);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user); sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo => sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{ {
@ -248,7 +244,7 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
var actual = await Assert.ThrowsAsync<UnauthorizedAccessException>(async () => var actual = await Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser, 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); 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; sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed; sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
.Returns(true);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user); sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo => sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{ {
@ -279,14 +271,14 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
new() new()
{ {
Id = sponsoringOrg.Id, Id = sponsoringOrg.Id,
Permissions = new Permissions(),
Type = organizationUserType Type = organizationUserType
} }
]); ]);
var actual = await Assert.ThrowsAsync<UnauthorizedAccessException>(async () => var actual = await Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser, 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); 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<CreateSponsorshipCommand> sutProvider) string friendlyName, Guid sponsorshipId, Guid currentUserId, string notes, SutProvider<CreateSponsorshipCommand> sutProvider)
{ {
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually; sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrg.UseAdminSponsoredFamilies = true;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed; sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
.Returns(true);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user); sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo => sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{ {
@ -317,6 +306,7 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
new() new()
{ {
Id = sponsoringOrg.Id, Id = sponsoringOrg.Id,
Permissions = new Permissions { ManageUsers = true },
Type = organizationUserType Type = organizationUserType
} }
]); ]);