1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-03 00:52:49 -05:00

Families for enterprise/stripe integrations (#1699)

* Add PlanSponsorshipType to static store

* Add sponsorship type to token and creates sponsorship

* PascalCase properties

* Require sponsorship for remove

* Create subscription sponsorship helper class

* Handle Sponsored subscription changes

* Add sponsorship id to subscription metadata

* Make sponsoring references nullable

This state indicates that a sponsorship has lapsed, but was not able to
be reverted for billing reasons

* WIP: Validate and remove subscriptions

* Update sponsorships on organization and org user delete

* Add friendly name to organization sponsorship
This commit is contained in:
Matt Gibson
2021-11-08 17:01:09 -06:00
committed by Justin Baur
parent 143be4273b
commit 45f6ec1781
42 changed files with 1060 additions and 188 deletions

View File

@ -42,8 +42,11 @@ namespace Bit.Api.Controllers
{
// TODO: validate has right to sponsor, send sponsorship email
var sponsoringOrgIdGuid = new Guid(sponsoringOrgId);
var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(model.PlanSponsorshipType)?.SponsoringProductType;
var sponsoringOrg = await _organizationRepository.GetByIdAsync(sponsoringOrgIdGuid);
if (sponsoringOrg == null || !PlanTypeHelper.HasEnterprisePlan(sponsoringOrg))
if (requiredSponsoringProductType == null ||
sponsoringOrg == null ||
StaticStore.GetPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value)
{
throw new BadRequestException("Specified Organization cannot sponsor other organizations.");
}
@ -64,14 +67,14 @@ namespace Bit.Api.Controllers
throw new BadRequestException("Can only sponsor one organization per Organization User.");
}
await _organizationsSponsorshipService.OfferSponsorshipAsync(sponsoringOrg, sponsoringOrgUser, model.PlanSponsorshipType, model.sponsoredEmail);
await _organizationsSponsorshipService.OfferSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
model.PlanSponsorshipType, model.SponsoredEmail, model.FriendlyName);
}
[HttpPost("sponsored/redeem/families-for-enterprise")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task RedeemSponsorship([FromQuery] string sponsorshipToken, [FromBody] OrganizationSponsorshipRedeemRequestModel model)
{
// TODO: parse out sponsorshipInfo
if (!await _organizationsSponsorshipService.ValidateRedemptionTokenAsync(sponsorshipToken))
{
throw new BadRequestException("Failed to parse sponsorship token.");
@ -99,9 +102,12 @@ namespace Bit.Api.Controllers
throw new BadRequestException("Cannot redeem a sponsorship offer for an organization that is already sponsored. Revoke existing sponsorship first.");
}
// Check org to sponsor's product type
var requiredSponsoredProductType = StaticStore.GetSponsoredPlan(model.PlanSponsorshipType)?.SponsoredProductType;
var organizationToSponsor = await _organizationRepository.GetByIdAsync(model.SponsoredOrganizationId);
// TODO: only current families plan?
if (organizationToSponsor == null || !PlanTypeHelper.HasFamiliesPlan(organizationToSponsor))
if (requiredSponsoredProductType == null ||
organizationToSponsor == null ||
StaticStore.GetPlan(organizationToSponsor.PlanType).Product != requiredSponsoredProductType.Value)
{
throw new BadRequestException("Can only redeem sponsorship offer on families organizations.");
}
@ -124,12 +130,19 @@ namespace Bit.Api.Controllers
var existingOrgSponsorship = await _organizationSponsorshipRepository
.GetBySponsoringOrganizationUserIdAsync(sponsoringOrgUserIdGuid);
if (existingOrgSponsorship == null)
if (existingOrgSponsorship == null || existingOrgSponsorship.SponsoredOrganizationId == null)
{
throw new BadRequestException("You are not currently sponsoring and organization.");
throw new BadRequestException("You are not currently sponsoring an organization.");
}
await _organizationsSponsorshipService.RemoveSponsorshipAsync(existingOrgSponsorship);
var sponsoredOrganization = await _organizationRepository
.GetByIdAsync(existingOrgSponsorship.SponsoredOrganizationId.Value);
if (sponsoredOrganization == null)
{
throw new BadRequestException("Unable to find the sponsored Organization.");
}
await _organizationsSponsorshipService.RemoveSponsorshipAsync(sponsoredOrganization, existingOrgSponsorship);
}
[HttpDelete("sponsored/{sponsoredOrgId}")]
@ -146,12 +159,20 @@ namespace Bit.Api.Controllers
var existingOrgSponsorship = await _organizationSponsorshipRepository
.GetBySponsoredOrganizationIdAsync(sponsoredOrgIdGuid);
if (existingOrgSponsorship == null)
if (existingOrgSponsorship == null || existingOrgSponsorship.SponsoredOrganizationId == null)
{
throw new BadRequestException("The requested organization is not currently being sponsored.");
}
await _organizationsSponsorshipService.RemoveSponsorshipAsync(existingOrgSponsorship);
var sponsoredOrganization = await _organizationRepository
.GetByIdAsync(existingOrgSponsorship.SponsoredOrganizationId.Value);
if (sponsoredOrganization == null)
{
throw new BadRequestException("Unable to find the sponsored Organization.");
}
await _organizationsSponsorshipService.RemoveSponsorshipAsync(sponsoredOrganization, existingOrgSponsorship);
}
}
}