mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 08:02: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:
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ namespace Bit.Billing.Controllers
|
||||
private readonly BillingSettings _billingSettings;
|
||||
private readonly IWebHostEnvironment _hostingEnvironment;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IOrganizationSponsorshipService _organizationSponsorshipService;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly ITransactionRepository _transactionRepository;
|
||||
private readonly IUserService _userService;
|
||||
@ -45,6 +46,7 @@ namespace Bit.Billing.Controllers
|
||||
IOptions<BillingSettings> billingSettings,
|
||||
IWebHostEnvironment hostingEnvironment,
|
||||
IOrganizationService organizationService,
|
||||
IOrganizationSponsorshipService organizationSponsorshipService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
ITransactionRepository transactionRepository,
|
||||
IUserService userService,
|
||||
@ -58,6 +60,7 @@ namespace Bit.Billing.Controllers
|
||||
_billingSettings = billingSettings?.Value;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_organizationService = organizationService;
|
||||
_organizationSponsorshipService = organizationSponsorshipService;
|
||||
_organizationRepository = organizationRepository;
|
||||
_transactionRepository = transactionRepository;
|
||||
_userService = userService;
|
||||
@ -136,6 +139,16 @@ namespace Bit.Billing.Controllers
|
||||
// org
|
||||
if (ids.Item1.HasValue)
|
||||
{
|
||||
var newEndPeriod = subscription.CurrentPeriodEnd;
|
||||
|
||||
// sponsored org
|
||||
if (IsSponsoredSubscription(subscription))
|
||||
{
|
||||
var sponsorshipValid = await _organizationSponsorshipService
|
||||
.ValidateSponsorshipAsync(ids.Item1.Value);
|
||||
// TODO: How do we return from this?
|
||||
}
|
||||
|
||||
await _organizationService.UpdateExpirationDateAsync(ids.Item1.Value,
|
||||
subscription.CurrentPeriodEnd);
|
||||
}
|
||||
@ -783,5 +796,8 @@ namespace Bit.Billing.Controllers
|
||||
}
|
||||
return subscription;
|
||||
}
|
||||
|
||||
private static bool IsSponsoredSubscription(Subscription subscription) =>
|
||||
StaticStore.SponsoredPlans.Any(p => p.StripePlanId == subscription.Id);
|
||||
}
|
||||
}
|
||||
|
@ -22,5 +22,7 @@ namespace Bit.Core.Enums
|
||||
GoogleInApp = 7,
|
||||
[Display(Name = "Check")]
|
||||
Check = 8,
|
||||
[Display(Name = "None")]
|
||||
None = 255,
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Core.Enums
|
||||
{
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Bit.Core.Models.Table;
|
||||
|
||||
namespace Bit.Core.Enums
|
||||
{
|
||||
@ -29,26 +27,6 @@ namespace Bit.Core.Enums
|
||||
[Display(Name = "Enterprise (Monthly)")]
|
||||
EnterpriseMonthly = 10,
|
||||
[Display(Name = "Enterprise (Annually)")]
|
||||
EnterpriseAnnually= 11,
|
||||
}
|
||||
|
||||
public static class PlanTypeHelper
|
||||
{
|
||||
private static readonly PlanType[] _freePlans = new[] { PlanType.Free };
|
||||
private static readonly PlanType[] _familiesPlans = new[] { PlanType.FamiliesAnnually, PlanType.FamiliesAnnually2019 };
|
||||
private static readonly PlanType[] _teamsPlans = new[] { PlanType.TeamsAnnually, PlanType.TeamsAnnually2019,
|
||||
PlanType.TeamsMonthly, PlanType.TeamsMonthly2019};
|
||||
private static readonly PlanType[] _enterprisePlans = new[] { PlanType.EnterpriseAnnually,
|
||||
PlanType.EnterpriseAnnually2019, PlanType.EnterpriseMonthly, PlanType.EnterpriseMonthly2019 };
|
||||
|
||||
private static bool HasPlan(PlanType[] planTypes, PlanType planType) => planTypes.Any(p => p == planType);
|
||||
public static bool HasFreePlan(Organization org) => IsFree(org.PlanType);
|
||||
public static bool IsFree(PlanType planType) => HasPlan(_freePlans, planType);
|
||||
public static bool HasFamiliesPlan(Organization org) => IsFamilies(org.PlanType);
|
||||
public static bool IsFamilies(PlanType planType) => HasPlan(_familiesPlans, planType);
|
||||
public static bool HasTeamsPlan(Organization org) => IsTeams(org.PlanType);
|
||||
public static bool IsTeams(PlanType planType) => HasPlan(_teamsPlans, planType);
|
||||
public static bool HasEnterprisePlan(Organization org) => IsEnterprise(org.PlanType);
|
||||
public static bool IsEnterprise(PlanType planType) => HasPlan(_enterprisePlans, planType);
|
||||
EnterpriseAnnually = 11,
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class OrganizationSponsorshipRedeemRequestModel
|
||||
{
|
||||
[Required]
|
||||
public PlanSponsorshipType PlanSponsorshipType { get; set; }
|
||||
[Required]
|
||||
public Guid SponsoredOrganizationId { get; set; }
|
||||
}
|
||||
|
@ -16,6 +16,9 @@ namespace Bit.Core.Models.Api.Request
|
||||
[Required]
|
||||
[StringLength(256)]
|
||||
[StrictEmailAddress]
|
||||
public string sponsoredEmail { get; set; }
|
||||
public string SponsoredEmail { get; set; }
|
||||
|
||||
[StringLength(256)]
|
||||
public string FriendlyName { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ namespace Bit.Core.Models.Api
|
||||
UserId = organization.UserId?.ToString();
|
||||
ProviderId = organization.ProviderId?.ToString();
|
||||
ProviderName = organization.ProviderName;
|
||||
FamilySponsorshipFriendlyName = organization.FamilySponsorshipFriendlyName;
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
@ -68,5 +69,6 @@ namespace Bit.Core.Models.Api
|
||||
public bool HasPublicAndPrivateKeys { get; set; }
|
||||
public string ProviderId { get; set; }
|
||||
public string ProviderName { get; set; }
|
||||
public string FamilySponsorshipFriendlyName { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Models.Table;
|
||||
|
||||
namespace Bit.Core.Models.Business
|
||||
{
|
||||
public class SponsoredOrganizationSubscription
|
||||
{
|
||||
public const string OrganizationSponsorhipIdMetadataKey = "OrganizationSponsorshipId";
|
||||
private readonly string _customerId;
|
||||
private readonly Organization _org;
|
||||
private readonly StaticStore.Plan _plan;
|
||||
private readonly List<Stripe.TaxRate> _taxRates;
|
||||
|
||||
public SponsoredOrganizationSubscription(Organization org, Stripe.Subscription existingSubscription)
|
||||
{
|
||||
_org = org;
|
||||
_customerId = org.GatewayCustomerId;
|
||||
_plan = Utilities.StaticStore.GetPlan(org.PlanType);
|
||||
_taxRates = existingSubscription.DefaultTaxRates;
|
||||
}
|
||||
|
||||
public SponsorOrganizationSubscriptionOptions GetSponsorSubscriptionOptions(OrganizationSponsorship sponsorship,
|
||||
int additionalSeats = 0, int additionalStorageGb = 0, bool premiumAccessAddon = false)
|
||||
{
|
||||
var sponsoredPlan = Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value);
|
||||
|
||||
var subCreateOptions = new SponsorOrganizationSubscriptionOptions(_customerId, _org, _plan,
|
||||
sponsoredPlan, _taxRates, additionalSeats, additionalStorageGb, premiumAccessAddon);
|
||||
|
||||
subCreateOptions.Metadata.Add(OrganizationSponsorhipIdMetadataKey, sponsorship.Id.ToString());
|
||||
return subCreateOptions;
|
||||
}
|
||||
|
||||
public OrganizationUpgradeSubscriptionOptions RemoveOrganizationSubscriptionOptions(int additionalSeats = 0,
|
||||
int additionalStorageGb = 0, bool premiumAccessAddon = false) =>
|
||||
new OrganizationUpgradeSubscriptionOptions(_customerId, _org, _plan, _taxRates,
|
||||
additionalSeats, additionalStorageGb, premiumAccessAddon);
|
||||
}
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
using Bit.Core.Models.Table;
|
||||
using Stripe;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Core.Models.Business
|
||||
{
|
||||
public class OrganizationSubscriptionOptionsBase : Stripe.SubscriptionCreateOptions
|
||||
{
|
||||
public OrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan plan, TaxInfo taxInfo, int additionalSeats, int additionalStorageGb, bool premiumAccessAddon)
|
||||
public OrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan plan,
|
||||
int additionalSeats, int additionalStorageGb, bool premiumAccessAddon)
|
||||
{
|
||||
Items = new List<SubscriptionItemOptions>();
|
||||
Metadata = new Dictionary<string, string>
|
||||
@ -14,15 +16,6 @@ namespace Bit.Core.Models.Business
|
||||
[org.GatewayIdField()] = org.Id.ToString()
|
||||
};
|
||||
|
||||
if (plan.StripePlanId != null)
|
||||
{
|
||||
Items.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Plan = plan.StripePlanId,
|
||||
Quantity = 1
|
||||
});
|
||||
}
|
||||
|
||||
if (additionalSeats > 0 && plan.StripeSeatPlanId != null)
|
||||
{
|
||||
Items.Add(new SubscriptionItemOptions
|
||||
@ -49,15 +42,53 @@ namespace Bit.Core.Models.Business
|
||||
Quantity = 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(taxInfo?.StripeTaxRateId))
|
||||
protected void AddPlanItem(StaticStore.Plan plan) => AddPlanItem(plan.StripePlanId);
|
||||
protected void AddPlanItem(StaticStore.SponsoredPlan sponsoredPlan) => AddPlanItem(sponsoredPlan.StripePlanId);
|
||||
protected void AddPlanItem(string stripePlanId)
|
||||
{
|
||||
if (stripePlanId != null)
|
||||
{
|
||||
DefaultTaxRates = new List<string>{ taxInfo.StripeTaxRateId };
|
||||
Items.Add(new SubscriptionItemOptions
|
||||
{
|
||||
Plan = stripePlanId,
|
||||
Quantity = 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected void AddTaxRateItem(TaxInfo taxInfo) => AddTaxRateItem(new List<string> { taxInfo.StripeTaxRateId });
|
||||
protected void AddTaxRateItem(List<Stripe.TaxRate> taxRates) => AddTaxRateItem(taxRates?.Select(t => t.Id).ToList());
|
||||
protected void AddTaxRateItem(List<string> taxRateIds)
|
||||
{
|
||||
if (taxRateIds != null && taxRateIds.Any())
|
||||
{
|
||||
DefaultTaxRates = taxRateIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class OrganizationPurchaseSubscriptionOptions : OrganizationSubscriptionOptionsBase
|
||||
public abstract class UnsponsoredOrganizationSubscriptionOptionsBase : OrganizationSubscriptionOptionsBase
|
||||
{
|
||||
public UnsponsoredOrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan plan, TaxInfo taxInfo,
|
||||
int additionalSeats, int additionalStorage, bool premiumAccessAddon) :
|
||||
base(org, plan, additionalSeats, additionalStorage, premiumAccessAddon)
|
||||
{
|
||||
AddPlanItem(plan);
|
||||
AddTaxRateItem(taxInfo);
|
||||
}
|
||||
public UnsponsoredOrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan plan, List<Stripe.TaxRate> taxInfo,
|
||||
int additionalSeats, int additionalStorage, bool premiumAccessAddon) :
|
||||
base(org, plan, additionalSeats, additionalStorage, premiumAccessAddon)
|
||||
{
|
||||
AddPlanItem(plan);
|
||||
AddTaxRateItem(taxInfo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class OrganizationPurchaseSubscriptionOptions : UnsponsoredOrganizationSubscriptionOptionsBase
|
||||
{
|
||||
public OrganizationPurchaseSubscriptionOptions(
|
||||
Organization org, StaticStore.Plan plan,
|
||||
@ -70,7 +101,7 @@ namespace Bit.Core.Models.Business
|
||||
}
|
||||
}
|
||||
|
||||
public class OrganizationUpgradeSubscriptionOptions : OrganizationSubscriptionOptionsBase
|
||||
public class OrganizationUpgradeSubscriptionOptions : UnsponsoredOrganizationSubscriptionOptionsBase
|
||||
{
|
||||
public OrganizationUpgradeSubscriptionOptions(
|
||||
string customerId, Organization org,
|
||||
@ -81,5 +112,43 @@ namespace Bit.Core.Models.Business
|
||||
{
|
||||
Customer = customerId;
|
||||
}
|
||||
public OrganizationUpgradeSubscriptionOptions(
|
||||
string customerId, Organization org,
|
||||
StaticStore.Plan plan, List<Stripe.TaxRate> taxInfo,
|
||||
int additionalSeats = 0, int additionalStorageGb = 0,
|
||||
bool premiumAccessAddon = false) :
|
||||
base(org, plan, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon)
|
||||
{
|
||||
Customer = customerId;
|
||||
}
|
||||
}
|
||||
|
||||
public class RemoveOrganizationSubscriptionOptions : OrganizationSubscriptionOptionsBase
|
||||
{
|
||||
public RemoveOrganizationSubscriptionOptions(string customerId, Organization org,
|
||||
StaticStore.Plan plan, List<string> existingTaxRateStripeIds,
|
||||
int additionalSeats = 0, int additionalStorageGb = 0,
|
||||
bool premiumAccessAddon = false) :
|
||||
base(org, plan, additionalSeats, additionalStorageGb, premiumAccessAddon)
|
||||
{
|
||||
Customer = customerId;
|
||||
AddPlanItem(plan);
|
||||
AddTaxRateItem(existingTaxRateStripeIds);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class SponsorOrganizationSubscriptionOptions : OrganizationSubscriptionOptionsBase
|
||||
{
|
||||
public SponsorOrganizationSubscriptionOptions(
|
||||
string customerId, Organization org, StaticStore.Plan existingPlan,
|
||||
StaticStore.SponsoredPlan sponsorshipPlan, List<Stripe.TaxRate> existingTaxRates, int additionalSeats = 0,
|
||||
int additionalStorageGb = 0, bool premiumAccessAddon = false) :
|
||||
base(org, existingPlan, additionalSeats, additionalStorageGb, premiumAccessAddon)
|
||||
{
|
||||
Customer = customerId;
|
||||
AddPlanItem(sponsorshipPlan);
|
||||
AddTaxRateItem(existingTaxRates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,5 +33,6 @@ namespace Bit.Core.Models.Data
|
||||
public string PrivateKey { get; set; }
|
||||
public Guid? ProviderId { get; set; }
|
||||
public string ProviderName { get; set; }
|
||||
public string FamilySponsorshipFriendlyName { get; set; }
|
||||
}
|
||||
}
|
||||
|
12
src/Core/Models/StaticStore/SponsoredPlan.cs
Normal file
12
src/Core/Models/StaticStore/SponsoredPlan.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.StaticStore
|
||||
{
|
||||
public class SponsoredPlan
|
||||
{
|
||||
public PlanSponsorshipType PlanSponsorshipType { get; set; }
|
||||
public ProductType SponsoredProductType { get; set; }
|
||||
public ProductType SponsoringProductType { get; set; }
|
||||
public string StripePlanId { get; set; }
|
||||
}
|
||||
}
|
@ -9,12 +9,12 @@ namespace Bit.Core.Models.Table
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid? InstallationId { get; set; }
|
||||
[Required]
|
||||
public Guid SponsoringOrganizationId { get; set; }
|
||||
[Required]
|
||||
public Guid SponsoringOrganizationUserId { get; set; }
|
||||
public Guid? SponsoringOrganizationId { get; set; }
|
||||
public Guid? SponsoringOrganizationUserId { get; set; }
|
||||
public Guid? SponsoredOrganizationId { get; set; }
|
||||
[MaxLength(256)]
|
||||
public string FriendlyName { get; set; }
|
||||
[MaxLength(256)]
|
||||
public string OfferedToEmail { get; set; }
|
||||
public PlanSponsorshipType? PlanSponsorshipType { get; set; }
|
||||
[Required]
|
||||
|
@ -26,7 +26,7 @@ namespace Bit.Core.Repositories.EntityFramework
|
||||
public DbSet<GroupUser> GroupUsers { get; set; }
|
||||
public DbSet<Installation> Installations { get; set; }
|
||||
public DbSet<Organization> Organizations { get; set; }
|
||||
public DbSet<OrganizationSponsorship> organizationSponsorships { get; set; }
|
||||
public DbSet<OrganizationSponsorship> OrganizationSponsorships { get; set; }
|
||||
public DbSet<OrganizationUser> OrganizationUsers { get; set; }
|
||||
public DbSet<Policy> Policies { get; set; }
|
||||
public DbSet<Provider> Providers { get; set; }
|
||||
|
@ -95,5 +95,29 @@ namespace Bit.Core.Repositories.EntityFramework
|
||||
{
|
||||
await OrganizationUpdateStorage(id);
|
||||
}
|
||||
|
||||
public override async Task DeleteAsync(Organization organization)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var orgUser = dbContext.FindAsync<EFModel.Organization>(organization.Id);
|
||||
var sponsorships = dbContext.OrganizationSponsorships
|
||||
.Where(os =>
|
||||
os.SponsoringOrganizationId == organization.Id ||
|
||||
os.SponsoredOrganizationId == organization.Id);
|
||||
dbContext.RemoveRange(sponsorships.Where(os => os.CloudSponsor));
|
||||
|
||||
Guid? UpdatedOrgId(Guid? orgId) => orgId == organization.Id ? null : organization.Id;
|
||||
foreach (var sponsorship in sponsorships.Where(os => !os.CloudSponsor))
|
||||
{
|
||||
sponsorship.SponsoredOrganizationId = UpdatedOrgId(sponsorship.SponsoredOrganizationId);
|
||||
sponsorship.SponsoringOrganizationId = UpdatedOrgId(sponsorship.SponsoringOrganizationId);
|
||||
}
|
||||
|
||||
dbContext.Remove(orgUser);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace Bit.Core.Repositories.EntityFramework
|
||||
public class OrganizationSponsorshipRepository : Repository<TableModel.OrganizationSponsorship, EFModel.OrganizationSponsorship, Guid>, IOrganizationSponsorshipRepository
|
||||
{
|
||||
public OrganizationSponsorshipRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) :
|
||||
base(serviceScopeFactory, mapper, (DatabaseContext context) => context.organizationSponsorships)
|
||||
base(serviceScopeFactory, mapper, (DatabaseContext context) => context.OrganizationSponsorships)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -67,12 +67,32 @@ namespace Bit.Core.Repositories.EntityFramework
|
||||
return organizationUsers.Select(u => u.Id).ToList();
|
||||
}
|
||||
|
||||
public override async Task DeleteAsync(OrganizationUser organizationUser) => await DeleteAsync(organizationUser.Id);
|
||||
public async Task DeleteAsync(Guid organizationUserId)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var orgUser = dbContext.FindAsync<EfModel.OrganizationUser>(organizationUserId);
|
||||
var sponsorships = dbContext.OrganizationSponsorships
|
||||
.Where(os => os.SponsoringOrganizationUserId != default &&
|
||||
os.SponsoringOrganizationUserId.Value == organizationUserId);
|
||||
dbContext.RemoveRange(sponsorships);
|
||||
dbContext.Remove(orgUser);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteManyAsync(IEnumerable<Guid> organizationUserIds)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var entities = dbContext.FindAsync<EfModel.OrganizationUser>(organizationUserIds);
|
||||
var sponsorships = dbContext.OrganizationSponsorships
|
||||
.Where(os => os.SponsoringOrganizationUserId != default &&
|
||||
organizationUserIds.Contains(os.SponsoringOrganizationUserId ?? default));
|
||||
dbContext.RemoveRange(sponsorships);
|
||||
dbContext.RemoveRange(entities);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
@ -16,8 +16,10 @@ namespace Bit.Core.Repositories.EntityFramework.Queries
|
||||
from po in po_g.DefaultIfEmpty()
|
||||
join p in dbContext.Providers on po.ProviderId equals p.Id into p_g
|
||||
from p in p_g.DefaultIfEmpty()
|
||||
join os in dbContext.OrganizationSponsorships on ou.Id equals os.SponsoringOrganizationUserId into os_g
|
||||
from os in os_g.DefaultIfEmpty()
|
||||
where ((su == null || !su.OrganizationId.HasValue) || su.OrganizationId == ou.OrganizationId)
|
||||
select new { ou, o, su, p };
|
||||
select new { ou, o, su, p, os };
|
||||
return query.Select(x => new OrganizationUserOrganizationDetails
|
||||
{
|
||||
OrganizationId = x.ou.OrganizationId,
|
||||
@ -48,6 +50,7 @@ namespace Bit.Core.Repositories.EntityFramework.Queries
|
||||
PrivateKey = x.o.PrivateKey,
|
||||
ProviderId = x.p.Id,
|
||||
ProviderName = x.p.Name,
|
||||
FamilySponsorshipFriendlyName = x.os.FriendlyName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Table;
|
||||
@ -7,8 +8,10 @@ namespace Bit.Core.Services
|
||||
public interface IOrganizationSponsorshipService
|
||||
{
|
||||
Task<bool> ValidateRedemptionTokenAsync(string encryptedToken);
|
||||
Task OfferSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, PlanSponsorshipType sponsorshipType, string sponsoredEmail);
|
||||
Task OfferSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
|
||||
PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName);
|
||||
Task SetUpSponsorshipAsync(OrganizationSponsorship sponsorship, Organization sponsoredOrganization);
|
||||
Task RemoveSponsorshipAsync(OrganizationSponsorship sponsorship);
|
||||
Task<bool> ValidateSponsorshipAsync(Guid sponsoredOrganizationId);
|
||||
Task RemoveSponsorshipAsync(Organization sponsoredOrganization, OrganizationSponsorship sponsorship);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
@ -10,13 +11,15 @@ namespace Bit.Core.Services
|
||||
{
|
||||
Task CancelAndRecoverChargesAsync(ISubscriber subscriber);
|
||||
Task<string> PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType,
|
||||
string paymentToken, Models.StaticStore.Plan plan, short additionalStorageGb, int additionalSeats,
|
||||
string paymentToken, Plan plan, short additionalStorageGb, int additionalSeats,
|
||||
bool premiumAccessAddon, TaxInfo taxInfo);
|
||||
Task<string> UpgradeFreeOrganizationAsync(Organization org, Models.StaticStore.Plan plan,
|
||||
Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship);
|
||||
Task<bool> RemoveOrganizationSponsorshipAsync(Organization org);
|
||||
Task<string> UpgradeFreeOrganizationAsync(Organization org, Plan plan,
|
||||
short additionalStorageGb, int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo);
|
||||
Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
|
||||
short additionalStorageGb, TaxInfo taxInfo);
|
||||
Task<string> AdjustSeatsAsync(Organization organization, Models.StaticStore.Plan plan, int additionalSeats, DateTime? prorationDate = null);
|
||||
Task<string> AdjustSeatsAsync(Organization organization, Plan plan, int additionalSeats, DateTime? prorationDate = null);
|
||||
Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId, DateTime? prorationDate = null);
|
||||
Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false,
|
||||
bool skipInAppPurchaseCheck = false);
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Repositories;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
@ -13,12 +14,18 @@ namespace Bit.Core.Services
|
||||
private const string TokenClearTextPrefix = "BWOrganizationSponsorship_";
|
||||
|
||||
private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IDataProtector _dataProtector;
|
||||
|
||||
public OrganizationSponsorshipService(IOrganizationSponsorshipRepository organizationSponsorshipRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IPaymentService paymentService,
|
||||
IDataProtector dataProtector)
|
||||
{
|
||||
_organizationSponsorshipRepository = organizationSponsorshipRepository;
|
||||
_organizationRepository = organizationRepository;
|
||||
_paymentService = paymentService;
|
||||
_dataProtector = dataProtector;
|
||||
}
|
||||
|
||||
@ -63,13 +70,16 @@ namespace Bit.Core.Services
|
||||
_dataProtector.Protect($"{FamiliesForEnterpriseTokenName} {sponsorshipId} {sponsorshipType}")
|
||||
);
|
||||
|
||||
public async Task OfferSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, PlanSponsorshipType sponsorshipType, string sponsoredEmail)
|
||||
public async Task OfferSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
|
||||
PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName)
|
||||
{
|
||||
var sponsorship = new OrganizationSponsorship
|
||||
{
|
||||
SponsoringOrganizationId = sponsoringOrg.Id,
|
||||
SponsoringOrganizationUserId = sponsoringOrgUser.Id,
|
||||
FriendlyName = friendlyName,
|
||||
OfferedToEmail = sponsoredEmail,
|
||||
PlanSponsorshipType = sponsorshipType,
|
||||
CloudSponsor = true,
|
||||
};
|
||||
|
||||
@ -78,6 +88,7 @@ namespace Bit.Core.Services
|
||||
sponsorship = await _organizationSponsorshipRepository.CreateAsync(sponsorship);
|
||||
|
||||
// TODO: send email to sponsoredEmail w/ redemption token link
|
||||
var _ = RedemptionToken(sponsorship.Id, sponsorshipType);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -91,14 +102,117 @@ namespace Bit.Core.Services
|
||||
|
||||
public async Task SetUpSponsorshipAsync(OrganizationSponsorship sponsorship, Organization sponsoredOrganization)
|
||||
{
|
||||
// TODO: set up sponsorship, remember remove offeredToEmail from sponsorship
|
||||
throw new NotImplementedException();
|
||||
if (sponsorship.PlanSponsorshipType == null)
|
||||
{
|
||||
throw new BadRequestException("Cannot set up sponsorship without a known sponsorship type.");
|
||||
}
|
||||
|
||||
// TODO: rollback?
|
||||
await _paymentService.SponsorOrganizationAsync(sponsoredOrganization, sponsorship);
|
||||
await _organizationRepository.UpsertAsync(sponsoredOrganization);
|
||||
|
||||
sponsorship.SponsoredOrganizationId = sponsoredOrganization.Id;
|
||||
sponsorship.OfferedToEmail = null;
|
||||
await _organizationSponsorshipRepository.UpsertAsync(sponsorship);
|
||||
}
|
||||
|
||||
public async Task RemoveSponsorshipAsync(OrganizationSponsorship sponsorship)
|
||||
public async Task<bool> ValidateSponsorshipAsync(Guid sponsoredOrganizationId)
|
||||
{
|
||||
// TODO: remove sponsorship
|
||||
throw new NotImplementedException();
|
||||
var sponsoredOrganization = await _organizationRepository.GetByIdAsync(sponsoredOrganizationId);
|
||||
var existingSponsorship = await _organizationSponsorshipRepository
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrganizationId);
|
||||
|
||||
if (existingSponsorship == null)
|
||||
{
|
||||
await RemoveSponsorshipAsync(sponsoredOrganization);
|
||||
// TODO on fail, mark org as disabled.
|
||||
return false;
|
||||
}
|
||||
|
||||
var validated = true;
|
||||
if (existingSponsorship.SponsoringOrganizationId == null || existingSponsorship.SponsoringOrganizationUserId == null)
|
||||
{
|
||||
await RemoveSponsorshipAsync(sponsoredOrganization);
|
||||
validated = false;
|
||||
}
|
||||
|
||||
var sponsoringOrganization = await _organizationRepository
|
||||
.GetByIdAsync(existingSponsorship.SponsoringOrganizationId.Value);
|
||||
if (!sponsoringOrganization.Enabled)
|
||||
{
|
||||
await RemoveSponsorshipAsync(sponsoredOrganization);
|
||||
validated = false;
|
||||
}
|
||||
|
||||
if (!validated && existingSponsorship.SponsoredOrganizationId != null)
|
||||
{
|
||||
existingSponsorship.TimesRenewedWithoutValidation += 1;
|
||||
existingSponsorship.SponsorshipLapsedDate ??= DateTime.UtcNow;
|
||||
|
||||
await _organizationSponsorshipRepository.UpsertAsync(existingSponsorship);
|
||||
if (existingSponsorship.TimesRenewedWithoutValidation >= 6)
|
||||
{
|
||||
sponsoredOrganization.Enabled = false;
|
||||
await _organizationRepository.UpsertAsync(sponsoredOrganization);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task RemoveSponsorshipAsync(Organization sponsoredOrganization, OrganizationSponsorship sponsorship = null)
|
||||
{
|
||||
var success = await _paymentService.RemoveOrganizationSponsorshipAsync(sponsoredOrganization);
|
||||
await _organizationRepository.UpsertAsync(sponsoredOrganization);
|
||||
|
||||
if (sponsorship == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
// Initialize the record as available
|
||||
sponsorship.SponsoredOrganizationId = null;
|
||||
sponsorship.FriendlyName = null;
|
||||
sponsorship.OfferedToEmail = null;
|
||||
sponsorship.PlanSponsorshipType = null;
|
||||
sponsorship.TimesRenewedWithoutValidation = 0;
|
||||
sponsorship.SponsorshipLapsedDate = null;
|
||||
|
||||
if (sponsorship.CloudSponsor || sponsorship.SponsorshipLapsedDate.HasValue)
|
||||
{
|
||||
await _organizationSponsorshipRepository.DeleteAsync(sponsorship);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _organizationSponsorshipRepository.UpsertAsync(sponsorship);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sponsorship.SponsoringOrganizationId = null;
|
||||
sponsorship.SponsoringOrganizationUserId = null;
|
||||
|
||||
if (!sponsorship.CloudSponsor)
|
||||
{
|
||||
// Sef-hosted sponsorship record
|
||||
// we need to make the existing sponsorship available, and add
|
||||
// a new sponsorship record to record the lapsed sponsorship
|
||||
var cleanSponsorship = new OrganizationSponsorship
|
||||
{
|
||||
InstallationId = sponsorship.InstallationId,
|
||||
SponsoringOrganizationId = sponsorship.SponsoringOrganizationId,
|
||||
SponsoringOrganizationUserId = sponsorship.SponsoringOrganizationUserId,
|
||||
CloudSponsor = sponsorship.CloudSponsor,
|
||||
};
|
||||
await _organizationSponsorshipRepository.UpsertAsync(cleanSponsorship);
|
||||
}
|
||||
|
||||
sponsorship.SponsorshipLapsedDate ??= DateTime.UtcNow;
|
||||
await _organizationSponsorshipRepository.UpsertAsync(sponsorship);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -192,6 +192,44 @@ namespace Bit.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship)
|
||||
{
|
||||
var customer = await _stripeAdapter.CustomerGetAsync(org.GatewayCustomerId);
|
||||
var sub = await _stripeAdapter.SubscriptionGetAsync(org.GatewaySubscriptionId);
|
||||
|
||||
var sponsoredSubscription = new SponsoredOrganizationSubscription(org, sub);
|
||||
|
||||
var subscription = await ChargeForNewSubscriptionAsync(org, customer, false,
|
||||
false, PaymentMethodType.None, sponsoredSubscription.GetSponsorSubscriptionOptions(sponsorship), null);
|
||||
org.GatewaySubscriptionId = subscription.Id;
|
||||
|
||||
org.ExpirationDate = subscription.CurrentPeriodEnd;
|
||||
}
|
||||
|
||||
public async Task<bool> RemoveOrganizationSponsorshipAsync(Organization org)
|
||||
{
|
||||
var customer = await _stripeAdapter.CustomerGetAsync(org.GatewayCustomerId);
|
||||
var sub = await _stripeAdapter.SubscriptionGetAsync(org.GatewaySubscriptionId);
|
||||
|
||||
var sponsoredSubscription = new SponsoredOrganizationSubscription(org, sub);
|
||||
var subCreateOptions = sponsoredSubscription.RemoveOrganizationSubscriptionOptions();
|
||||
|
||||
var (stripePaymentMethod, paymentMethodType) = IdentifyPaymentMethod(customer, subCreateOptions);
|
||||
var subscription = await ChargeForNewSubscriptionAsync(org, customer, false,
|
||||
stripePaymentMethod, paymentMethodType, subCreateOptions, null);
|
||||
|
||||
if (subscription.Status == "incomplete")
|
||||
{
|
||||
// TODO: revert
|
||||
return false;
|
||||
}
|
||||
org.GatewaySubscriptionId = subscription.Id;
|
||||
org.Enabled = true;
|
||||
org.ExpirationDate = subscription.CurrentPeriodEnd;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<string> UpgradeFreeOrganizationAsync(Organization org, StaticStore.Plan plan,
|
||||
short additionalStorageGb, int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo)
|
||||
{
|
||||
@ -227,6 +265,29 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
var subCreateOptions = new OrganizationUpgradeSubscriptionOptions(customer.Id, org, plan, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon);
|
||||
var (stripePaymentMethod, paymentMethodType) = IdentifyPaymentMethod(customer, subCreateOptions);
|
||||
|
||||
var subscription = await ChargeForNewSubscriptionAsync(org, customer, false,
|
||||
stripePaymentMethod, paymentMethodType, subCreateOptions, null);
|
||||
org.GatewaySubscriptionId = subscription.Id;
|
||||
|
||||
if (subscription.Status == "incomplete" &&
|
||||
subscription.LatestInvoice?.PaymentIntent?.Status == "requires_action")
|
||||
{
|
||||
org.Enabled = false;
|
||||
return subscription.LatestInvoice.PaymentIntent.ClientSecret;
|
||||
}
|
||||
else
|
||||
{
|
||||
org.Enabled = true;
|
||||
org.ExpirationDate = subscription.CurrentPeriodEnd;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private (bool stripePaymentMethod, PaymentMethodType PaymentMethodType) IdentifyPaymentMethod(
|
||||
Stripe.Customer customer, Stripe.SubscriptionCreateOptions subCreateOptions)
|
||||
{
|
||||
var stripePaymentMethod = false;
|
||||
var paymentMethodType = PaymentMethodType.Credit;
|
||||
var hasBtCustomerId = customer.Metadata.ContainsKey("btCustomerId");
|
||||
@ -265,23 +326,7 @@ namespace Bit.Core.Services
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var subscription = await ChargeForNewSubscriptionAsync(org, customer, false,
|
||||
stripePaymentMethod, paymentMethodType, subCreateOptions, null);
|
||||
org.GatewaySubscriptionId = subscription.Id;
|
||||
|
||||
if (subscription.Status == "incomplete" &&
|
||||
subscription.LatestInvoice?.PaymentIntent?.Status == "requires_action")
|
||||
{
|
||||
org.Enabled = false;
|
||||
return subscription.LatestInvoice.PaymentIntent.ClientSecret;
|
||||
}
|
||||
else
|
||||
{
|
||||
org.Enabled = true;
|
||||
org.ExpirationDate = subscription.CurrentPeriodEnd;
|
||||
return null;
|
||||
}
|
||||
return (stripePaymentMethod, paymentMethodType);
|
||||
}
|
||||
|
||||
public async Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType,
|
||||
|
@ -1,6 +1,8 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.Models.Table;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Core.Utilities
|
||||
{
|
||||
@ -475,5 +477,19 @@ namespace Bit.Core.Utilities
|
||||
|
||||
public static IDictionary<GlobalEquivalentDomainsType, IEnumerable<string>> GlobalDomains { get; set; }
|
||||
public static IEnumerable<Plan> Plans { get; set; }
|
||||
public static IEnumerable<SponsoredPlan> SponsoredPlans { get; set; } = new[]
|
||||
{
|
||||
new SponsoredPlan
|
||||
{
|
||||
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
|
||||
SponsoredProductType = ProductType.Families,
|
||||
SponsoringProductType = ProductType.Enterprise,
|
||||
StripePlanId = "2021-enterprise-sponsored-families-org-monthly"
|
||||
}
|
||||
};
|
||||
public static Plan GetPlan(PlanType planType) =>
|
||||
Plans.FirstOrDefault(p => p.Type == planType);
|
||||
public static SponsoredPlan GetSponsoredPlan(PlanSponsorshipType planSponsorshipType) =>
|
||||
SponsoredPlans.FirstOrDefault(p => p.PlanSponsorshipType == planSponsorshipType);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
CREATE PROCEDURE [dbo].[OrganizationSponsorship_OrganizationDeleted]
|
||||
@OrganizationId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
UPDATE
|
||||
[dbo].[OrganizationSponsorship]
|
||||
SET
|
||||
[SponsoringOrganizationId] = NULL
|
||||
WHERE
|
||||
[SponsoringOrganizationId] = @OrganizationId AND
|
||||
[CloudSponsor] = 0
|
||||
|
||||
UPDATE
|
||||
[dbo].[OrganizationSponsorship]
|
||||
SET
|
||||
[SponsoredOrganizationId] = NULL
|
||||
WHERE
|
||||
[SponsoredOrganizationId] = @OrganizationId AND
|
||||
[CloudSponsor] = 0
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[OrganizationSponsorship]
|
||||
WHERE
|
||||
[CloudSponsor] = 1 AND
|
||||
([SponsoredOrganizationId] = @OrganizationId OR
|
||||
[SponsoringOrganizationId] = @OrganizationId)
|
||||
END
|
||||
GO
|
@ -0,0 +1,13 @@
|
||||
CREATE PROCEDURE [dbo].[OrganizationSponsorship_OrganizationUserDeleted]
|
||||
@OrganizationUserId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[OrganizationSponsorship]
|
||||
WHERE
|
||||
[SponsoringOrganizationUserId] = @OrganizationUserId
|
||||
END
|
||||
GO
|
@ -0,0 +1,24 @@
|
||||
CREATE PROCEDURE [dbo].[OrganizationSponsorship_OrganizationUsersDeleted]
|
||||
@SponsoringOrganizationUserIds [dbo].[GuidIdArray] READONLY
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SET @BatchSize = 100;
|
||||
|
||||
WHILE @BatchSize > 0
|
||||
BEGIN
|
||||
BEGIN TRANSACTION OrganizationSponsorship_DeleteOUs
|
||||
|
||||
DELETE TOP(@BatchSize) OS
|
||||
FROM
|
||||
[dbo].[OrganiozationSponsorship] OS
|
||||
INNER JOIN
|
||||
@Ids I ON I.Id = OS.SponsoringOrganizationUserId
|
||||
|
||||
SET @BatchSize = @@ROWCOUNT
|
||||
|
||||
COMMIT TRANSACTION OrganizationSponsorship_DeleteOUs
|
||||
END
|
||||
END
|
||||
GO
|
@ -34,9 +34,19 @@ BEGIN
|
||||
WHERE
|
||||
[OrganizationUserId] = @Id
|
||||
|
||||
EXEC [dbo].[OrganizationUser_DeleteById] @Id
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[OrganizationUser]
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
END
|
||||
END
|
||||
GO
|
||||
|
||||
|
||||
IF OBJECT_ID('[dbo].[OrganizationUser_DeleteByIds]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[OrganizationUser_DeleteByIds]
|
||||
END
|
||||
GO
|
||||
|
@ -61,6 +61,7 @@ BEGIN
|
||||
COMMIT TRANSACTION GoupUser_DeleteMany_GroupUsers
|
||||
END
|
||||
|
||||
EXEC [dbo].[OrganizationSponsorship_OrganizationUsersDeleted] @Ids
|
||||
|
||||
SET @BatchSize = 100;
|
||||
|
||||
|
@ -57,6 +57,8 @@ BEGIN
|
||||
WHERE
|
||||
[OrganizationId] = @Id
|
||||
|
||||
EXEC[dbo].[OrganizationSponsorship_OrganizationDeleted] @Id
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[Organization]
|
||||
@ -65,3 +67,4 @@ BEGIN
|
||||
|
||||
COMMIT TRANSACTION Organization_DeleteById
|
||||
END
|
||||
GO
|
||||
|
@ -1,8 +1,8 @@
|
||||
CREATE TABLE [dbo].[OrganizationSponsorship] (
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[InstallationId] UNIQUEIDENTIFIER NULL,
|
||||
[SponsoringOrganizationId] UNIQUEIDENTIFIER NOT NULL,
|
||||
[SponsoringOrganizationUserID] UNIQUEIDENTIFIER NOT NULL,
|
||||
[SponsoringOrganizationId] UNIQUEIDENTIFIER NULL,
|
||||
[SponsoringOrganizationUserID] UNIQUEIDENTIFIER NULL,
|
||||
[SponsoredOrganizationId] UNIQUEIDENTIFIER NULL,
|
||||
[OfferedToEmail] NVARCHAR (256) NULL,
|
||||
[PlanSponsorshipType] TINYINT NULL,
|
||||
@ -19,24 +19,25 @@ CREATE TABLE [dbo].[OrganizationSponsorship] (
|
||||
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_OrganizationSponsorship_InstallationId]
|
||||
ON [dbo].[Organization]([Id] ASC, [InstallationId] ASC)
|
||||
ON [dbo].[OrganizationSponsorship]([InstallationId] ASC)
|
||||
WHERE [InstallationId] IS NOT NULL;
|
||||
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_OrganizationSponsorship_SponsoringOrganizationId]
|
||||
ON [dbo].[Organization]([Id] ASC, [SponsoringOrganizationId] ASC)
|
||||
ON [dbo].[OrganizationSponsorship]([SponsoringOrganizationId] ASC)
|
||||
WHERE [SponsoringOrganizationId] IS NOT NULL;
|
||||
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_OrganizationSponsorship_SponsoringOrganizationUserId]
|
||||
ON [dbo].[Organization]([Id] ASC, [SponsorginOrganizationUserID] ASC)
|
||||
ON [dbo].[OrganizationSponsorship]([SponsoringOrganizationUserID] ASC)
|
||||
WHERE [SponsoringOrganizationUserID] IS NOT NULL;
|
||||
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_OrganizationSponsorship_OfferedToEmail]
|
||||
ON [dbo].[Organization]([Id] ASC, [OfferedToEmail] ASC)
|
||||
ON [dbo].[OrganizationSponsorship]([OfferedToEmail] ASC)
|
||||
WHERE [OfferedToEmail] IS NOT NULL;
|
||||
|
||||
GO
|
||||
CREATE NONCLUSTERED INDEX [IX_OrganizationSponsorship_SponsoredOrganizationID]
|
||||
ON [dbo].[Organization]([Id] ASC, [SponsoredOrganizationId] ASC)
|
||||
ON [dbo].[OrganizationSponsorship]([SponsoredOrganizationId] ASC)
|
||||
WHERE [SponsoredOrganizationId] IS NOT NULL;
|
||||
|
||||
|
@ -16,6 +16,7 @@ using Bit.Core.Repositories;
|
||||
using Bit.Core.Models.Api.Request;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Api.Test.Controllers
|
||||
{
|
||||
@ -24,27 +25,29 @@ namespace Bit.Api.Test.Controllers
|
||||
public class OrganizationSponsorshipsControllerTests
|
||||
{
|
||||
public static IEnumerable<object[]> EnterprisePlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => PlanTypeHelper.IsEnterprise(p)).Select(p => new object[] { p });
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
|
||||
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => !PlanTypeHelper.IsEnterprise(p)).Select(p => new object[] { p });
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
|
||||
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => !PlanTypeHelper.IsFamilies(p)).Select(p => new object[] { p });
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
|
||||
public async Task CreateSponsorship_BadSponsoringOrgPlan_ThrowsBadRequest(PlanType sponsoringOrgPlan, Organization org,
|
||||
SutProvider<OrganizationSponsorshipsController> sutProvider)
|
||||
OrganizationSponsorshipRequestModel model, SutProvider<OrganizationSponsorshipsController> sutProvider)
|
||||
{
|
||||
org.PlanType = sponsoringOrgPlan;
|
||||
model.PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.CreateSponsorship(org.Id.ToString(), null));
|
||||
sutProvider.Sut.CreateSponsorship(org.Id.ToString(), model));
|
||||
|
||||
Assert.Contains("Specified Organization cannot sponsor other organizations.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.OfferSponsorshipAsync(default, default, default, default);
|
||||
.OfferSponsorshipAsync(default, default, default, default, default);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
|
||||
@ -73,7 +76,7 @@ namespace Bit.Api.Test.Controllers
|
||||
Assert.Contains("Only confirm users can sponsor other organizations.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.OfferSponsorshipAsync(default, default, default, default);
|
||||
.OfferSponsorshipAsync(default, default, default, default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -96,7 +99,7 @@ namespace Bit.Api.Test.Controllers
|
||||
Assert.Contains("Can only create organization sponsorships for yourself.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.OfferSponsorshipAsync(default, default, default, default);
|
||||
.OfferSponsorshipAsync(default, default, default, default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -121,7 +124,7 @@ namespace Bit.Api.Test.Controllers
|
||||
Assert.Contains("Can only sponsor one organization per Organization User.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.OfferSponsorshipAsync(default, default, default, default);
|
||||
.OfferSponsorshipAsync(default, default, default, default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -272,7 +275,7 @@ namespace Bit.Api.Test.Controllers
|
||||
Assert.Contains("Can only revoke a sponsorship you granted.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.RemoveSponsorshipAsync(default);
|
||||
.RemoveSponsorshipAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -293,10 +296,58 @@ namespace Bit.Api.Test.Controllers
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RevokeSponsorship(sponsoringOrgUser.Id.ToString()));
|
||||
|
||||
Assert.Contains("You are not currently sponsoring and organization.", exception.Message);
|
||||
Assert.Contains("You are not currently sponsoring an organization.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.RemoveSponsorshipAsync(default);
|
||||
.RemoveSponsorshipAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeSponsorship_SponsorshipNotRedeemed_ThrowsBadRequest(OrganizationUser sponsoringOrgUser,
|
||||
OrganizationSponsorship sponsorship, SutProvider<OrganizationSponsorshipsController> sutProvider)
|
||||
{
|
||||
sponsorship.SponsoredOrganizationId = null;
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(sponsoringOrgUser.UserId);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(sponsoringOrgUser.Id)
|
||||
.Returns(sponsoringOrgUser);
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoringOrganizationUserIdAsync(Arg.Is<Guid>(v => v != sponsoringOrgUser.Id))
|
||||
.Returns(sponsorship);
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoringOrganizationUserIdAsync(sponsoringOrgUser.Id)
|
||||
.Returns((OrganizationSponsorship)sponsorship);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RevokeSponsorship(sponsoringOrgUser.Id.ToString()));
|
||||
|
||||
Assert.Contains("You are not currently sponsoring an organization.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.RemoveSponsorshipAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeSponsorship_SponsoredOrgNotFound_ThrowsBadRequest(OrganizationUser sponsoringOrgUser,
|
||||
OrganizationSponsorship sponsorship, SutProvider<OrganizationSponsorshipsController> sutProvider)
|
||||
{
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(sponsoringOrgUser.UserId);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(sponsoringOrgUser.Id)
|
||||
.Returns(sponsoringOrgUser);
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoringOrganizationUserIdAsync(sponsoringOrgUser.Id)
|
||||
.Returns(sponsorship);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RevokeSponsorship(sponsoringOrgUser.Id.ToString()));
|
||||
|
||||
Assert.Contains("Unable to find the sponsored Organization.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.RemoveSponsorshipAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -312,7 +363,7 @@ namespace Bit.Api.Test.Controllers
|
||||
Assert.Contains("Only the owner of an organization can remove sponsorship.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.RemoveSponsorshipAsync(default);
|
||||
.RemoveSponsorshipAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -334,7 +385,26 @@ namespace Bit.Api.Test.Controllers
|
||||
Assert.Contains("The requested organization is not currently being sponsored.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.RemoveSponsorshipAsync(default);
|
||||
.RemoveSponsorshipAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RemoveSponsorship_SponsoredOrgNotFound_ThrowsBadRequest(Organization sponsoredOrg,
|
||||
OrganizationSponsorship sponsorship, SutProvider<OrganizationSponsorshipsController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(Arg.Any<Guid>()).Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id)
|
||||
.Returns(sponsorship);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RemoveSponsorship(sponsoredOrg.Id.ToString()));
|
||||
|
||||
Assert.Contains("Unable to find the sponsored Organization.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.RemoveSponsorshipAsync(default, default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Table;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Enums
|
||||
{
|
||||
public class PlanTypeHelperTests
|
||||
{
|
||||
private static IEnumerable<PlanType> PlanArchetypeArray(PlanType planType) => new PlanType?[] {
|
||||
PlanTypeHelper.HasFreePlan(new Organization {PlanType = planType}) ? planType : null,
|
||||
PlanTypeHelper.HasFamiliesPlan(new Organization {PlanType = planType}) ? planType : null,
|
||||
PlanTypeHelper.HasTeamsPlan(new Organization {PlanType = planType}) ? planType : null,
|
||||
PlanTypeHelper.HasEnterprisePlan(new Organization {PlanType = planType}) ? planType : null,
|
||||
}.Where(v => v.HasValue).Select(v => (PlanType)v);
|
||||
|
||||
public static IEnumerable<object[]> PlanTypes => Enum.GetValues<PlanType>().Select(p => new object[] { p });
|
||||
public static IEnumerable<object[]> PlanTypesExceptCustom =>
|
||||
Enum.GetValues<PlanType>().Except(new[] { PlanType.Custom }).Select(p => new object[] { p });
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(PlanTypesExceptCustom))]
|
||||
public void NonCustomPlanTypesBelongToPlanArchetype(PlanType planType)
|
||||
{
|
||||
Assert.Contains(planType, PlanArchetypeArray(planType));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(PlanTypesExceptCustom))]
|
||||
public void PlanTypesBelongToOnlyOneArchetype(PlanType planType)
|
||||
{
|
||||
Console.WriteLine(planType);
|
||||
Assert.Single(PlanArchetypeArray(planType));
|
||||
}
|
||||
}
|
||||
}
|
@ -34,15 +34,16 @@ namespace Bit.Core.Test.Services
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task OfferSponsorship_CreatesSponsorship(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
|
||||
string sponsoredEmail, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
string sponsoredEmail, string friendlyName, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.OfferSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
|
||||
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail);
|
||||
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName);
|
||||
|
||||
var expectedSponsorship = new OrganizationSponsorship
|
||||
{
|
||||
SponsoringOrganizationId = sponsoringOrg.Id,
|
||||
SponsoringOrganizationUserId = sponsoringOrgUser.Id,
|
||||
FriendlyName = friendlyName,
|
||||
OfferedToEmail = sponsoredEmail,
|
||||
CloudSponsor = true,
|
||||
};
|
||||
@ -55,7 +56,7 @@ namespace Bit.Core.Test.Services
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task OfferSponsorship_CreateSponsorshipThrows_RevertsDatabase(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
|
||||
string sponsoredEmail, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
string sponsoredEmail, string friendlyName, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
var expectedException = new Exception();
|
||||
OrganizationSponsorship createdSponsorship = null;
|
||||
@ -68,7 +69,7 @@ namespace Bit.Core.Test.Services
|
||||
|
||||
var actualException = await Assert.ThrowsAsync<Exception>(() =>
|
||||
sutProvider.Sut.OfferSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
|
||||
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail));
|
||||
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName));
|
||||
Assert.Same(expectedException, actualException);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
|
||||
|
@ -4,9 +4,10 @@ BEGIN
|
||||
CREATE TABLE [dbo].[OrganizationSponsorship] (
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[InstallationId] UNIQUEIDENTIFIER NULL,
|
||||
[SponsoringOrganizationId] UNIQUEIDENTIFIER NOT NULL,
|
||||
[SponsoringOrganizationUserID] UNIQUEIDENTIFIER NOT NULL,
|
||||
[SponsoringOrganizationId] UNIQUEIDENTIFIER NULL,
|
||||
[SponsoringOrganizationUserID] UNIQUEIDENTIFIER NULL,
|
||||
[SponsoredOrganizationId] UNIQUEIDENTIFIER NULL,
|
||||
[FriendlyName] NVARCHAR(256) NULL,
|
||||
[OfferedToEmail] NVARCHAR (256) NULL,
|
||||
[PlanSponsorshipType] TINYINT NULL,
|
||||
[CloudSponsor] BIT NULL,
|
||||
@ -35,6 +36,7 @@ IF NOT EXISTS(SELECT name FROM sys.indexes WHERE name = 'IX_OrganizationSponsors
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX [IX_OrganizationSponsorship_SponsoringOrganizationId]
|
||||
ON [dbo].[OrganizationSponsorship]([SponsoringOrganizationId] ASC)
|
||||
WHERE [SponsoringOrganizationId] IS NOT NULL;
|
||||
END
|
||||
GO
|
||||
|
||||
@ -42,6 +44,7 @@ IF NOT EXISTS(SELECT name FROM sys.indexes WHERE name = 'IX_OrganizationSponsors
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX [IX_OrganizationSponsorship_SponsoringOrganizationUserId]
|
||||
ON [dbo].[OrganizationSponsorship]([SponsoringOrganizationUserID] ASC)
|
||||
WHERE [SponsoringOrganizationUserID] IS NOT NULL;
|
||||
END
|
||||
GO
|
||||
|
||||
@ -114,6 +117,7 @@ CREATE PROCEDURE [dbo].[OrganizationSponsorship_Create]
|
||||
@SponsoringOrganizationId UNIQUEIDENTIFIER,
|
||||
@SponsoringOrganizationUserID UNIQUEIDENTIFIER,
|
||||
@SponsoredOrganizationId UNIQUEIDENTIFIER,
|
||||
@FriendlyName NVARCHAR(256),
|
||||
@OfferedToEmail NVARCHAR(256),
|
||||
@PlanSponsorshipType TINYINT,
|
||||
@CloudSponsor BIT,
|
||||
@ -131,6 +135,7 @@ BEGIN
|
||||
[SponsoringOrganizationId],
|
||||
[SponsoringOrganizationUserID],
|
||||
[SponsoredOrganizationId],
|
||||
[FriendlyName],
|
||||
[OfferedToEmail],
|
||||
[PlanSponsorshipType],
|
||||
[CloudSponsor],
|
||||
@ -145,6 +150,7 @@ BEGIN
|
||||
@SponsoringOrganizationId,
|
||||
@SponsoringOrganizationUserID,
|
||||
@SponsoredOrganizationId,
|
||||
@FriendlyName,
|
||||
@OfferedToEmail,
|
||||
@PlanSponsorshipType,
|
||||
@CloudSponsor,
|
||||
@ -168,6 +174,7 @@ CREATE PROCEDURE [dbo].[OrganizationSponsorship_Update]
|
||||
@SponsoringOrganizationId UNIQUEIDENTIFIER,
|
||||
@SponsoringOrganizationUserID UNIQUEIDENTIFIER,
|
||||
@SponsoredOrganizationId UNIQUEIDENTIFIER,
|
||||
@FriendlyName NVARCHAR(256),
|
||||
@OfferedToEmail NVARCHAR(256),
|
||||
@PlanSponsorshipType TINYINT,
|
||||
@CloudSponsor BIT,
|
||||
@ -185,6 +192,7 @@ BEGIN
|
||||
[SponsoringOrganizationId] = @SponsoringOrganizationId,
|
||||
[SponsoringOrganizationUserID] = @SponsoringOrganizationUserID,
|
||||
[SponsoredOrganizationId] = @SponsoredOrganizationId,
|
||||
[FriendlyName] = @FriendlyName,
|
||||
[OfferedToEmail] = @OfferedToEmail,
|
||||
[PlanSponsorshipType] = @PlanSponsorshipType,
|
||||
[CloudSponsor] = @CloudSponsor,
|
||||
@ -290,3 +298,365 @@ BEGIN
|
||||
[OfferedToEmail] = @OfferedToEmail
|
||||
END
|
||||
GO
|
||||
|
||||
-- OrganizationSponsorship_OrganizationDeleted
|
||||
IF OBJECT_ID('[dbo].[OrganizationSponsorship_OrganizationDeleted]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[OrganizationSponsorship_OrganizationDeleted]
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE [dbo].[OrganizationSponsorship_OrganizationDeleted]
|
||||
@OrganizationId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
UPDATE
|
||||
[dbo].[OrganizationSponsorship]
|
||||
SET
|
||||
[SponsoringOrganizationId] = NULL
|
||||
WHERE
|
||||
[SponsoringOrganizationId] = @OrganizationId AND
|
||||
[CloudSponsor] = 0
|
||||
|
||||
UPDATE
|
||||
[dbo].[OrganizationSponsorship]
|
||||
SET
|
||||
[SponsoredOrganizationId] = NULL
|
||||
WHERE
|
||||
[SponsoredOrganizationId] = @OrganizationId AND
|
||||
[CloudSponsor] = 0
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[OrganizationSponsorship]
|
||||
WHERE
|
||||
[CloudSponsor] = 1 AND
|
||||
([SponsoredOrganizationId] = @OrganizationId OR
|
||||
[SponsoringOrganizationId] = @OrganizationId)
|
||||
END
|
||||
GO
|
||||
|
||||
-- OrganizationSponsorship_OrganizationUserDeleted
|
||||
IF OBJECT_ID('[dbo].[OrganizationSponsorship_OrganizationUserDeleted]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[OrganizationSponsorship_OrganizationUserDeleted]
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE [dbo].[OrganizationSponsorship_OrganizationUserDeleted]
|
||||
@OrganizationUserId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[OrganizationSponsorship]
|
||||
WHERE
|
||||
[SponsoringOrganizationUserId] = @OrganizationUserId
|
||||
END
|
||||
GO
|
||||
|
||||
-- OrganizationSponsorship_OrganizationUsersDeleted
|
||||
IF OBJECT_ID('[dbo].[OrganizationSponsorship_OrganizationUsersDeleted]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[OrganizationSponsorship_OrganizationUsersDeleted]
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE [dbo].[OrganizationSponsorship_OrganizationUsersDeleted]
|
||||
@SponsoringOrganizationUserIds [dbo].[GuidIdArray] READONLY
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
DECLARE @BatchSize INT = 100
|
||||
|
||||
WHILE @BatchSize > 0
|
||||
BEGIN
|
||||
BEGIN TRANSACTION OS_DeleteMany_OUs
|
||||
|
||||
DELETE TOP(@BatchSize) OS
|
||||
FROM
|
||||
[dbo].[OrganizationSponsorship] OS
|
||||
INNER JOIN
|
||||
@SponsoringOrganizationUserIds I ON I.Id = OS.SponsoringOrganizationUserId
|
||||
|
||||
SET @BatchSize = @@ROWCOUNT
|
||||
|
||||
COMMIT TRANSACTION OS_DeleteMany_OUs
|
||||
END
|
||||
END
|
||||
GO
|
||||
|
||||
-- Update Organization delete sprocs to handle organization sponsorships
|
||||
IF OBJECT_ID('[dbo].[Organization_DeleteById]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[Organization_DeleteById]
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE [dbo].[Organization_DeleteById]
|
||||
@Id UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @Id
|
||||
|
||||
DECLARE @BatchSize INT = 100
|
||||
WHILE @BatchSize > 0
|
||||
BEGIN
|
||||
BEGIN TRANSACTION Organization_DeleteById_Ciphers
|
||||
|
||||
DELETE TOP(@BatchSize)
|
||||
FROM
|
||||
[dbo].[Cipher]
|
||||
WHERE
|
||||
[UserId] IS NULL
|
||||
AND [OrganizationId] = @Id
|
||||
|
||||
SET @BatchSize = @@ROWCOUNT
|
||||
|
||||
COMMIT TRANSACTION Organization_DeleteById_Ciphers
|
||||
END
|
||||
|
||||
BEGIN TRANSACTION Organization_DeleteById
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[SsoUser]
|
||||
WHERE
|
||||
[OrganizationId] = @Id
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[SsoConfig]
|
||||
WHERE
|
||||
[OrganizationId] = @Id
|
||||
|
||||
DELETE CU
|
||||
FROM
|
||||
[dbo].[CollectionUser] CU
|
||||
INNER JOIN
|
||||
[dbo].[OrganizationUser] OU ON [CU].[OrganizationUserId] = [OU].[Id]
|
||||
WHERE
|
||||
[OU].[OrganizationId] = @Id
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[OrganizationUser]
|
||||
WHERE
|
||||
[OrganizationId] = @Id
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[ProviderOrganization]
|
||||
WHERE
|
||||
[OrganizationId] = @Id
|
||||
|
||||
EXEC[dbo].[OrganizationSponsorship_OrganizationDeleted] @Id
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[Organization]
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
|
||||
COMMIT TRANSACTION Organization_DeleteById
|
||||
END
|
||||
GO
|
||||
|
||||
-- Update Organization User delete sprocs to handle organization sponsorships
|
||||
IF OBJECT_ID('[dbo].[OrganizationUser_DeleteById]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[OrganizationUser_DeleteById]
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE [dbo].[OrganizationUser_DeleteById]
|
||||
@Id UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationUserId] @Id
|
||||
|
||||
DECLARE @OrganizationId UNIQUEIDENTIFIER
|
||||
DECLARE @UserId UNIQUEIDENTIFIER
|
||||
|
||||
SELECT
|
||||
@OrganizationId = [OrganizationId],
|
||||
@UserId = [UserId]
|
||||
FROM
|
||||
[dbo].[OrganizationUser]
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
|
||||
IF @OrganizationId IS NOT NULL AND @UserId IS NOT NULL
|
||||
BEGIN
|
||||
EXEC [dbo].[SsoUser_Delete] @UserId, @OrganizationId
|
||||
END
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[CollectionUser]
|
||||
WHERE
|
||||
[OrganizationUserId] = @Id
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[GroupUser]
|
||||
WHERE
|
||||
[OrganizationUserId] = @Id
|
||||
|
||||
EXEC [dbo].[OrganizationUser_DeleteById] @Id
|
||||
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[OrganizationUser]
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
END
|
||||
GO
|
||||
|
||||
|
||||
IF OBJECT_ID('[dbo].[OrganizationUser_DeleteByIds]') IS NOT NULL
|
||||
BEGIN
|
||||
DROP PROCEDURE [dbo].[OrganizationUser_DeleteByIds]
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE PROCEDURE [dbo].[OrganizationUser_DeleteByIds]
|
||||
@Ids [dbo].[GuidIdArray] READONLY
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationUserIds] @Ids
|
||||
|
||||
DECLARE @UserAndOrganizationIds [dbo].[TwoGuidIdArray]
|
||||
|
||||
INSERT INTO @UserAndOrganizationIds
|
||||
(Id1, Id2)
|
||||
SELECT
|
||||
UserId,
|
||||
OrganizationId
|
||||
FROM
|
||||
[dbo].[OrganizationUser] OU
|
||||
INNER JOIN
|
||||
@Ids OUIds ON OUIds.Id = OU.Id
|
||||
WHERE
|
||||
UserId IS NOT NULL AND
|
||||
OrganizationId IS NOT NULL
|
||||
|
||||
BEGIN
|
||||
EXEC [dbo].[SsoUser_DeleteMany] @UserAndOrganizationIds
|
||||
END
|
||||
|
||||
DECLARE @BatchSize INT = 100
|
||||
|
||||
-- Delete CollectionUsers
|
||||
WHILE @BatchSize > 0
|
||||
BEGIN
|
||||
BEGIN TRANSACTION CollectionUser_DeleteMany_CUs
|
||||
|
||||
DELETE TOP(@BatchSize) CU
|
||||
FROM
|
||||
[dbo].[CollectionUser] CU
|
||||
INNER JOIN
|
||||
@Ids I ON I.Id = CU.OrganizationUserId
|
||||
|
||||
SET @BatchSize = @@ROWCOUNT
|
||||
|
||||
COMMIT TRANSACTION CollectionUser_DeleteMany_CUs
|
||||
END
|
||||
|
||||
SET @BatchSize = 100;
|
||||
|
||||
-- Delete GroupUsers
|
||||
WHILE @BatchSize > 0
|
||||
BEGIN
|
||||
BEGIN TRANSACTION GroupUser_DeleteMany_GroupUsers
|
||||
|
||||
DELETE TOP(@BatchSize) GU
|
||||
FROM
|
||||
[dbo].[GroupUser] GU
|
||||
INNER JOIN
|
||||
@Ids I ON I.Id = GU.OrganizationUserId
|
||||
|
||||
SET @BatchSize = @@ROWCOUNT
|
||||
|
||||
COMMIT TRANSACTION GoupUser_DeleteMany_GroupUsers
|
||||
END
|
||||
|
||||
EXEC [dbo].[OrganizationSponsorship_OrganizationUsersDeleted] @Ids
|
||||
|
||||
SET @BatchSize = 100;
|
||||
|
||||
-- Delete OrganizationUsers
|
||||
WHILE @BatchSize > 0
|
||||
BEGIN
|
||||
BEGIN TRANSACTION OrganizationUser_DeleteMany_OUs
|
||||
|
||||
DELETE TOP(@BatchSize) OU
|
||||
FROM
|
||||
[dbo].[OrganizationUser] OU
|
||||
INNER JOIN
|
||||
@Ids I ON I.Id = OU.Id
|
||||
|
||||
SET @BatchSize = @@ROWCOUNT
|
||||
|
||||
COMMIT TRANSACTION OrganizationUser_DeleteMany_OUs
|
||||
END
|
||||
END
|
||||
GO
|
||||
|
||||
-- OrganizationUserOrganizationDetailsView update
|
||||
ALTER VIEW [dbo].[OrganizationUserOrganizationDetailsView]
|
||||
AS
|
||||
SELECT
|
||||
OU.[UserId],
|
||||
OU.[OrganizationId],
|
||||
O.[Name],
|
||||
O.[Enabled],
|
||||
O.[UsePolicies],
|
||||
O.[UseSso],
|
||||
O.[UseGroups],
|
||||
O.[UseDirectory],
|
||||
O.[UseEvents],
|
||||
O.[UseTotp],
|
||||
O.[Use2fa],
|
||||
O.[UseApi],
|
||||
O.[UseResetPassword],
|
||||
O.[SelfHost],
|
||||
O.[UsersGetPremium],
|
||||
O.[Seats],
|
||||
O.[MaxCollections],
|
||||
O.[MaxStorageGb],
|
||||
O.[Identifier],
|
||||
OU.[Key],
|
||||
OU.[ResetPasswordKey],
|
||||
O.[PublicKey],
|
||||
O.[PrivateKey],
|
||||
OU.[Status],
|
||||
OU.[Type],
|
||||
SU.[ExternalId] SsoExternalId,
|
||||
OU.[Permissions],
|
||||
PO.[ProviderId],
|
||||
P.[Name] ProviderName,
|
||||
OS.[FriendlyName] FamilySponsorshipFriendlyName
|
||||
FROM
|
||||
[dbo].[OrganizationUser] OU
|
||||
INNER JOIN
|
||||
[dbo].[Organization] O ON O.[Id] = OU.[OrganizationId]
|
||||
LEFT JOIN
|
||||
[dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId]
|
||||
LEFT JOIN
|
||||
[dbo].[ProviderOrganization] PO ON PO.[OrganizationId] = O.[Id]
|
||||
LEFT JOIN
|
||||
[dbo].[Provider] P ON P.[Id] = PO.[ProviderId]
|
||||
LEFT JOIN
|
||||
[dbo].[OrganizationSponsorship] OS ON OS.[SponsoringOrganizationUserId] = OU.[Id]
|
||||
|
@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace Bit.MySqlMigrations.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20211104164838_OrganizationSponsorship")]
|
||||
[Migration("20211108225243_OrganizationSponsorship")]
|
||||
partial class OrganizationSponsorship
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@ -595,6 +595,10 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.Property<bool>("CloudSponsor")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("FriendlyName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("varchar(256)");
|
||||
|
||||
b.Property<Guid?>("InstallationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
@ -611,10 +615,10 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.Property<Guid?>("SponsoredOrganizationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("SponsoringOrganizationId")
|
||||
b.Property<Guid?>("SponsoringOrganizationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("SponsoringOrganizationUserId")
|
||||
b.Property<Guid?>("SponsoringOrganizationUserId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime?>("SponsorshipLapsedDate")
|
||||
@ -1356,9 +1360,7 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
|
||||
b.HasOne("Bit.Core.Models.EntityFramework.Organization", "SponsoringOrganization")
|
||||
.WithMany()
|
||||
.HasForeignKey("SponsoringOrganizationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
.HasForeignKey("SponsoringOrganizationId");
|
||||
|
||||
b.Navigation("Installation");
|
||||
|
@ -20,9 +20,11 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
InstallationId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
SponsoringOrganizationId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
SponsoringOrganizationUserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
SponsoringOrganizationId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
SponsoringOrganizationUserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
SponsoredOrganizationId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
FriendlyName = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
OfferedToEmail = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
PlanSponsorshipType = table.Column<byte>(type: "tinyint unsigned", nullable: true),
|
||||
@ -51,7 +53,7 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
column: x => x.SponsoringOrganizationId,
|
||||
principalTable: "Organization",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
@ -593,6 +593,10 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.Property<bool>("CloudSponsor")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("FriendlyName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("varchar(256)");
|
||||
|
||||
b.Property<Guid?>("InstallationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
@ -609,10 +613,10 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
b.Property<Guid?>("SponsoredOrganizationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("SponsoringOrganizationId")
|
||||
b.Property<Guid?>("SponsoringOrganizationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("SponsoringOrganizationUserId")
|
||||
b.Property<Guid?>("SponsoringOrganizationUserId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime?>("SponsorshipLapsedDate")
|
||||
@ -1354,9 +1358,7 @@ namespace Bit.MySqlMigrations.Migrations
|
||||
|
||||
b.HasOne("Bit.Core.Models.EntityFramework.Organization", "SponsoringOrganization")
|
||||
.WithMany()
|
||||
.HasForeignKey("SponsoringOrganizationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
.HasForeignKey("SponsoringOrganizationId");
|
||||
|
||||
b.Navigation("Installation");
|
||||
|
||||
|
@ -5,9 +5,10 @@ ALTER TABLE `User` ADD `UsesCryptoAgent` tinyint(1) NOT NULL DEFAULT FALSE;
|
||||
CREATE TABLE `OrganizationSponsorship` (
|
||||
`Id` char(36) COLLATE ascii_general_ci NOT NULL,
|
||||
`InstallationId` char(36) COLLATE ascii_general_ci NULL,
|
||||
`SponsoringOrganizationId` char(36) COLLATE ascii_general_ci NOT NULL,
|
||||
`SponsoringOrganizationUserId` char(36) COLLATE ascii_general_ci NOT NULL,
|
||||
`SponsoringOrganizationId` char(36) COLLATE ascii_general_ci NULL,
|
||||
`SponsoringOrganizationUserId` char(36) COLLATE ascii_general_ci NULL,
|
||||
`SponsoredOrganizationId` char(36) COLLATE ascii_general_ci NULL,
|
||||
`FriendlyName` varchar(256) CHARACTER SET utf8mb4 NULL,
|
||||
`OfferedToEmail` varchar(256) CHARACTER SET utf8mb4 NULL,
|
||||
`PlanSponsorshipType` tinyint unsigned NULL,
|
||||
`CloudSponsor` tinyint(1) NOT NULL,
|
||||
@ -17,7 +18,7 @@ CREATE TABLE `OrganizationSponsorship` (
|
||||
CONSTRAINT `PK_OrganizationSponsorship` PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_OrganizationSponsorship_Installation_InstallationId` FOREIGN KEY (`InstallationId`) REFERENCES `Installation` (`Id`) ON DELETE RESTRICT,
|
||||
CONSTRAINT `FK_OrganizationSponsorship_Organization_SponsoredOrganizationId` FOREIGN KEY (`SponsoredOrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE RESTRICT,
|
||||
CONSTRAINT `FK_OrganizationSponsorship_Organization_SponsoringOrganizationId` FOREIGN KEY (`SponsoringOrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE
|
||||
CONSTRAINT `FK_OrganizationSponsorship_Organization_SponsoringOrganizationId` FOREIGN KEY (`SponsoringOrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE RESTRICT
|
||||
) CHARACTER SET utf8mb4;
|
||||
|
||||
CREATE INDEX `IX_OrganizationSponsorship_InstallationId` ON `OrganizationSponsorship` (`InstallationId`);
|
||||
@ -27,6 +28,6 @@ CREATE INDEX `IX_OrganizationSponsorship_SponsoredOrganizationId` ON `Organizati
|
||||
CREATE INDEX `IX_OrganizationSponsorship_SponsoringOrganizationId` ON `OrganizationSponsorship` (`SponsoringOrganizationId`);
|
||||
|
||||
INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`)
|
||||
VALUES ('20211104164838_OrganizationSponsorship', '5.0.9');
|
||||
VALUES ('20211108225243_OrganizationSponsorship', '5.0.9');
|
||||
|
||||
COMMIT;
|
||||
|
@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
namespace Bit.PostgresMigrations.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20211104164532_OrganizationSponsorship")]
|
||||
[Migration("20211108225011_OrganizationSponsorship")]
|
||||
partial class OrganizationSponsorship
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@ -599,6 +599,10 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.Property<bool>("CloudSponsor")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("FriendlyName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<Guid?>("InstallationId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
@ -615,10 +619,10 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.Property<Guid?>("SponsoredOrganizationId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("SponsoringOrganizationId")
|
||||
b.Property<Guid?>("SponsoringOrganizationId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("SponsoringOrganizationUserId")
|
||||
b.Property<Guid?>("SponsoringOrganizationUserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime?>("SponsorshipLapsedDate")
|
||||
@ -1365,9 +1369,7 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
|
||||
b.HasOne("Bit.Core.Models.EntityFramework.Organization", "SponsoringOrganization")
|
||||
.WithMany()
|
||||
.HasForeignKey("SponsoringOrganizationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
.HasForeignKey("SponsoringOrganizationId");
|
||||
|
||||
b.Navigation("Installation");
|
||||
|
@ -20,9 +20,10 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
InstallationId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
SponsoringOrganizationId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
SponsoringOrganizationUserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
SponsoringOrganizationId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
SponsoringOrganizationUserId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
SponsoredOrganizationId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
FriendlyName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
OfferedToEmail = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
PlanSponsorshipType = table.Column<byte>(type: "smallint", nullable: true),
|
||||
CloudSponsor = table.Column<bool>(type: "boolean", nullable: false),
|
||||
@ -50,7 +51,7 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
column: x => x.SponsoringOrganizationId,
|
||||
principalTable: "Organization",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
@ -597,6 +597,10 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.Property<bool>("CloudSponsor")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("FriendlyName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<Guid?>("InstallationId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
@ -613,10 +617,10 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
b.Property<Guid?>("SponsoredOrganizationId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("SponsoringOrganizationId")
|
||||
b.Property<Guid?>("SponsoringOrganizationId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("SponsoringOrganizationUserId")
|
||||
b.Property<Guid?>("SponsoringOrganizationUserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime?>("SponsorshipLapsedDate")
|
||||
@ -1363,9 +1367,7 @@ namespace Bit.PostgresMigrations.Migrations
|
||||
|
||||
b.HasOne("Bit.Core.Models.EntityFramework.Organization", "SponsoringOrganization")
|
||||
.WithMany()
|
||||
.HasForeignKey("SponsoringOrganizationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
.HasForeignKey("SponsoringOrganizationId");
|
||||
|
||||
b.Navigation("Installation");
|
||||
|
||||
|
@ -5,9 +5,10 @@ ALTER TABLE "User" ADD "UsesCryptoAgent" boolean NOT NULL DEFAULT FALSE;
|
||||
CREATE TABLE "OrganizationSponsorship" (
|
||||
"Id" uuid NOT NULL,
|
||||
"InstallationId" uuid NULL,
|
||||
"SponsoringOrganizationId" uuid NOT NULL,
|
||||
"SponsoringOrganizationUserId" uuid NOT NULL,
|
||||
"SponsoringOrganizationId" uuid NULL,
|
||||
"SponsoringOrganizationUserId" uuid NULL,
|
||||
"SponsoredOrganizationId" uuid NULL,
|
||||
"FriendlyName" character varying(256) NULL,
|
||||
"OfferedToEmail" character varying(256) NULL,
|
||||
"PlanSponsorshipType" smallint NULL,
|
||||
"CloudSponsor" boolean NOT NULL,
|
||||
@ -17,7 +18,7 @@ CREATE TABLE "OrganizationSponsorship" (
|
||||
CONSTRAINT "PK_OrganizationSponsorship" PRIMARY KEY ("Id"),
|
||||
CONSTRAINT "FK_OrganizationSponsorship_Installation_InstallationId" FOREIGN KEY ("InstallationId") REFERENCES "Installation" ("Id") ON DELETE RESTRICT,
|
||||
CONSTRAINT "FK_OrganizationSponsorship_Organization_SponsoredOrganizationId" FOREIGN KEY ("SponsoredOrganizationId") REFERENCES "Organization" ("Id") ON DELETE RESTRICT,
|
||||
CONSTRAINT "FK_OrganizationSponsorship_Organization_SponsoringOrganization~" FOREIGN KEY ("SponsoringOrganizationId") REFERENCES "Organization" ("Id") ON DELETE CASCADE
|
||||
CONSTRAINT "FK_OrganizationSponsorship_Organization_SponsoringOrganization~" FOREIGN KEY ("SponsoringOrganizationId") REFERENCES "Organization" ("Id") ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
CREATE INDEX "IX_OrganizationSponsorship_InstallationId" ON "OrganizationSponsorship" ("InstallationId");
|
||||
@ -27,6 +28,6 @@ CREATE INDEX "IX_OrganizationSponsorship_SponsoredOrganizationId" ON "Organizati
|
||||
CREATE INDEX "IX_OrganizationSponsorship_SponsoringOrganizationId" ON "OrganizationSponsorship" ("SponsoringOrganizationId");
|
||||
|
||||
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
|
||||
VALUES ('20211104164532_OrganizationSponsorship', '5.0.9');
|
||||
VALUES ('20211108225011_OrganizationSponsorship', '5.0.9');
|
||||
|
||||
COMMIT;
|
||||
|
Reference in New Issue
Block a user