diff --git a/src/Core/Enums/PlanType.cs b/src/Core/Enums/PlanType.cs index 48c6ba39e..7b3495178 100644 --- a/src/Core/Enums/PlanType.cs +++ b/src/Core/Enums/PlanType.cs @@ -3,7 +3,7 @@ public enum PlanType : byte { Free = 0, - PersonalAnnually = 1, + FamilyAnnually = 1, TeamsMonthly = 2, TeamsAnnually = 3, EnterpriseMonthly = 4, diff --git a/src/Core/Models/Business/OrganizationLicense.cs b/src/Core/Models/Business/OrganizationLicense.cs index 56c1f8262..5e65357a4 100644 --- a/src/Core/Models/Business/OrganizationLicense.cs +++ b/src/Core/Models/Business/OrganizationLicense.cs @@ -19,7 +19,7 @@ namespace Bit.Core.Models.Business public OrganizationLicense(Organization org, BillingInfo billingInfo, Guid installationId, ILicensingService licenseService) { - Version = 1; + Version = 2; LicenseKey = org.LicenseKey; InstallationId = installationId; Id = org.Id; @@ -36,6 +36,7 @@ namespace Bit.Core.Models.Business UseTotp = org.UseTotp; MaxStorageGb = org.MaxStorageGb; SelfHost = org.SelfHost; + UsersGetPremium = org.UsersGetPremium; Issued = DateTime.UtcNow; if(billingInfo?.Subscription == null) @@ -91,6 +92,7 @@ namespace Bit.Core.Models.Business public bool UseTotp { get; set; } public short? MaxStorageGb { get; set; } public bool SelfHost { get; set; } + public bool UsersGetPremium { get; set; } public int Version { get; set; } public DateTime Issued { get; set; } public DateTime? Refresh { get; set; } @@ -104,7 +106,7 @@ namespace Bit.Core.Models.Business public byte[] GetDataBytes(bool forHash = false) { string data = null; - if(Version == 1) + if(Version == 1 || Version == 2) { var props = typeof(OrganizationLicense) .GetProperties(BindingFlags.Public | BindingFlags.Instance) @@ -147,7 +149,7 @@ namespace Bit.Core.Models.Business return false; } - if(Version == 1) + if(Version == 1 || Version == 2) { return InstallationId == globalSettings.Installation.Id && SelfHost; } @@ -164,9 +166,9 @@ namespace Bit.Core.Models.Business return false; } - if(Version == 1) + if(Version == 1 || Version == 2) { - return + var valid = globalSettings.Installation.Id == InstallationId && organization.LicenseKey.Equals(LicenseKey) && organization.Enabled == Enabled && @@ -178,6 +180,13 @@ namespace Bit.Core.Models.Business organization.UseTotp == UseTotp && organization.SelfHost == SelfHost && organization.Name.Equals(Name); + + if(valid && Version == 2) + { + valid = organization.UsersGetPremium = UsersGetPremium; + } + + return valid; } else { diff --git a/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs b/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs index 4a90f588d..438236328 100644 --- a/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs +++ b/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs @@ -10,6 +10,8 @@ namespace Bit.Core.Models.Data public bool UseGroups { get; set; } public bool UseDirectory { get; set; } public bool UseTotp { get; set; } + public bool SelfHost { get; set; } + public bool UsersGetPremium { get; set; } public int Seats { get; set; } public int MaxCollections { get; set; } public short? MaxStorageGb { get; set; } diff --git a/src/Core/Models/StaticStore/Plan.cs b/src/Core/Models/StaticStore/Plan.cs index 94b43227c..025994057 100644 --- a/src/Core/Models/StaticStore/Plan.cs +++ b/src/Core/Models/StaticStore/Plan.cs @@ -23,5 +23,6 @@ namespace Bit.Core.Models.StaticStore public bool Disabled { get; set; } public int? TrialPeriodDays { get; set; } public bool SelfHost { get; set; } + public bool UsersGetPremium { get; set; } } } diff --git a/src/Core/Models/Table/Organization.cs b/src/Core/Models/Table/Organization.cs index 8e3cac000..63a5ac88d 100644 --- a/src/Core/Models/Table/Organization.cs +++ b/src/Core/Models/Table/Organization.cs @@ -25,6 +25,7 @@ namespace Bit.Core.Models.Table public bool UseDirectory { get; set; } public bool UseTotp { get; set; } public bool SelfHost { get; set; } + public bool UsersGetPremium { get; set; } public long? Storage { get; set; } public short? MaxStorageGb { get; set; } public GatewayType? Gateway { get; set; } diff --git a/src/Core/Services/Implementations/LicensingService.cs b/src/Core/Services/Implementations/LicensingService.cs index 2ff14cbe4..39272f84e 100644 --- a/src/Core/Services/Implementations/LicensingService.cs +++ b/src/Core/Services/Implementations/LicensingService.cs @@ -112,10 +112,10 @@ namespace Bit.Core.Services foreach(var user in nonPremiumUsers) { var details = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id); - if(details.Any(d => d.Enabled)) + if(details.Any(d => d.SelfHost && d.UsersGetPremium && d.Enabled)) { - _logger.LogInformation("Granting premium to user {0}({1}) because they are in an active organization.", - user.Id, user.Email); + _logger.LogInformation("Granting premium to user {0}({1}) because they are in an active organization " + + "with premium features.", user.Id, user.Email); user.Premium = true; user.MaxStorageGb = 10240; // 10 TB @@ -170,7 +170,7 @@ namespace Bit.Core.Services if(!valid) { var details = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id); - valid = details.Any(d => d.Enabled); + valid = details.Any(d => d.SelfHost && d.UsersGetPremium && d.Enabled); if(valid && (!string.IsNullOrWhiteSpace(user.LicenseKey) || user.PremiumExpirationDate.HasValue)) { diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index a4b30da53..0980c6ea5 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -527,6 +527,7 @@ namespace Bit.Core.Services UseDirectory = plan.UseDirectory, UseTotp = plan.UseTotp, SelfHost = plan.SelfHost, + UsersGetPremium = plan.UsersGetPremium, Plan = plan.Name, Gateway = plan.Type == PlanType.Free ? null : (GatewayType?)GatewayType.Stripe, GatewayCustomerId = customer?.Id, @@ -581,6 +582,7 @@ namespace Bit.Core.Services UseTotp = license.UseTotp, Plan = license.Plan, SelfHost = license.SelfHost, + UsersGetPremium = license.UsersGetPremium, Gateway = null, GatewayCustomerId = null, GatewaySubscriptionId = null, @@ -597,8 +599,8 @@ namespace Bit.Core.Services Directory.CreateDirectory(dir); File.WriteAllText($"{dir}/{organization.Id}.json", JsonConvert.SerializeObject(license, Formatting.Indented)); - // self-hosted org users get premium access - if(!owner.Premium && result.Item1.Enabled) + // self-hosted org users get premium access on some plans + if(organization.UsersGetPremium && !owner.Premium && result.Item1.Enabled) { owner.Premium = true; owner.MaxStorageGb = 10240; // 10 TB @@ -1003,7 +1005,7 @@ namespace Bit.Core.Services await _mailService.SendOrganizationConfirmedEmailAsync(org.Name, user.Email); // self-hosted org users get premium access - if(_globalSettings.SelfHosted && !user.Premium && org.Enabled) + if(_globalSettings.SelfHosted && !user.Premium && org.UsersGetPremium && org.Enabled) { user.Premium = true; user.MaxStorageGb = 10240; // 10 TB diff --git a/src/Core/Utilities/StaticStore.cs b/src/Core/Utilities/StaticStore.cs index 6e98dddc6..743c45484 100644 --- a/src/Core/Utilities/StaticStore.cs +++ b/src/Core/Utilities/StaticStore.cs @@ -101,20 +101,18 @@ namespace Bit.Core.Utilities }, new Plan { - Type = PlanType.PersonalAnnually, + Type = PlanType.FamilyAnnually, BaseSeats = 5, BasePrice = 12, - SeatPrice = 12, - CanBuyAdditionalSeats = true, - MaxAdditionalSeats = 5, - Name = "Personal", + CanBuyAdditionalSeats = false, + Name = "Family", StripePlanId = "personal-org-annually", - StripeSeatPlanId = "personal-org-seat-annually", StripStoragePlanId = "storage-gb-annually", UpgradeSortOrder = 1, TrialPeriodDays = 7, UseTotp = true, - MaxStorageGb = 1 + MaxStorageGb = 1, + SelfHost = true }, new Plan { @@ -165,7 +163,8 @@ namespace Bit.Core.Utilities UseDirectory = true, UseTotp = true, MaxStorageGb = 1, - SelfHost = true + SelfHost = true, + UsersGetPremium = true }, new Plan { @@ -184,7 +183,8 @@ namespace Bit.Core.Utilities UseDirectory = true, UseTotp = true, MaxStorageGb = 1, - SelfHost = true + SelfHost = true, + UsersGetPremium = true } }; diff --git a/src/Sql/dbo/Stored Procedures/Organization_Create.sql b/src/Sql/dbo/Stored Procedures/Organization_Create.sql index 1cd60f3d2..d8b68ddd4 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Create.sql @@ -16,6 +16,7 @@ @UseDirectory BIT, @UseTotp BIT, @SelfHost BIT, + @UsersGetPremium BIT, @Storage BIGINT, @MaxStorageGb SMALLINT, @Gateway TINYINT, @@ -49,6 +50,7 @@ BEGIN [UseDirectory], [UseTotp], [SelfHost], + [UsersGetPremium], [Storage], [MaxStorageGb], [Gateway], @@ -79,6 +81,7 @@ BEGIN @UseDirectory, @UseTotp, @SelfHost, + @UsersGetPremium, @Storage, @MaxStorageGb, @Gateway, diff --git a/src/Sql/dbo/Stored Procedures/Organization_Update.sql b/src/Sql/dbo/Stored Procedures/Organization_Update.sql index 2e82b701d..3c672c36f 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Update.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Update.sql @@ -16,6 +16,7 @@ @UseDirectory BIT, @UseTotp BIT, @SelfHost BIT, + @UsersGetPremium BIT, @Storage BIGINT, @MaxStorageGb SMALLINT, @Gateway TINYINT, @@ -26,7 +27,6 @@ @ExpirationDate DATETIME2(7), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7) - AS BEGIN SET NOCOUNT ON @@ -50,6 +50,7 @@ BEGIN [UseDirectory] = @UseDirectory, [UseTotp] = @UseTotp, [SelfHost] = @SelfHost, + [UsersGetPremium] = @UsersGetPremium, [Storage] = @Storage, [MaxStorageGb] = @MaxStorageGb, [Gateway] = @Gateway, diff --git a/src/Sql/dbo/Tables/Organization.sql b/src/Sql/dbo/Tables/Organization.sql index 4008926a5..80e46b327 100644 --- a/src/Sql/dbo/Tables/Organization.sql +++ b/src/Sql/dbo/Tables/Organization.sql @@ -16,6 +16,7 @@ [UseDirectory] BIT NOT NULL, [UseTotp] BIT NOT NULL, [SelfHost] BIT NOT NULL, + [UsersGetPremium] BIT NOT NULL, [Storage] BIGINT NULL, [MaxStorageGb] SMALLINT NULL, [Gateway] TINYINT NULL, diff --git a/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql b/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql index 6a5478689..c79322e88 100644 --- a/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql @@ -8,6 +8,8 @@ SELECT O.[UseGroups], O.[UseDirectory], O.[UseTotp], + O.[SelfHost], + O.[UsersGetPremium], O.[Seats], O.[MaxCollections], O.[MaxStorageGb], diff --git a/util/Setup/DbScripts/2017-11-06_00_FamilyPlanAdjustments.sql b/util/Setup/DbScripts/2017-11-06_00_FamilyPlanAdjustments.sql new file mode 100644 index 000000000..1bc25d753 --- /dev/null +++ b/util/Setup/DbScripts/2017-11-06_00_FamilyPlanAdjustments.sql @@ -0,0 +1,236 @@ +IF COL_LENGTH('[dbo].[Organization]', 'UsersGetPremium') IS NULL +BEGIN + ALTER TABLE + [dbo].[Organization] + ADD + [UsersGetPremium] BIT NULL +END +GO + +UPDATE + [dbo].[Organization] +SET + [UsersGetPremium] = (CASE WHEN [PlanType] = 5 OR [PlanType] = 4 THEN 1 ELSE 0 END) +GO + +UPDATE + [dbo].[Organization] +SET + [Plan] = 'Family' +WHERE + [PlanType] = 1 +GO + +ALTER TABLE + [dbo].[Organization] +ALTER COLUMN + [UsersGetPremium] BIT NOT NULL +GO + +IF OBJECT_ID('[dbo].[Organization_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Organization_Create] +END +GO + +CREATE PROCEDURE [dbo].[Organization_Create] + @Id UNIQUEIDENTIFIER, + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(50), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats SMALLINT, + @MaxCollections SMALLINT, + @UseGroups BIT, + @UseDirectory BIT, + @UseTotp BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Organization] + ( + [Id], + [Name], + [BusinessName], + [BusinessAddress1], + [BusinessAddress2], + [BusinessAddress3], + [BusinessCountry], + [BusinessTaxNumber], + [BillingEmail], + [Plan], + [PlanType], + [Seats], + [MaxCollections], + [UseGroups], + [UseDirectory], + [UseTotp], + [SelfHost], + [UsersGetPremium], + [Storage], + [MaxStorageGb], + [Gateway], + [GatewayCustomerId], + [GatewaySubscriptionId], + [Enabled], + [LicenseKey], + [ExpirationDate], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @Name, + @BusinessName, + @BusinessAddress1, + @BusinessAddress2, + @BusinessAddress3, + @BusinessCountry, + @BusinessTaxNumber, + @BillingEmail, + @Plan, + @PlanType, + @Seats, + @MaxCollections, + @UseGroups, + @UseDirectory, + @UseTotp, + @SelfHost, + @UsersGetPremium, + @Storage, + @MaxStorageGb, + @Gateway, + @GatewayCustomerId, + @GatewaySubscriptionId, + @Enabled, + @LicenseKey, + @ExpirationDate, + @CreationDate, + @RevisionDate + ) +END +GO + +IF OBJECT_ID('[dbo].[Organization_Update]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Organization_Update] +END +GO + +CREATE PROCEDURE [dbo].[Organization_Update] + @Id UNIQUEIDENTIFIER, + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(50), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats SMALLINT, + @MaxCollections SMALLINT, + @UseGroups BIT, + @UseDirectory BIT, + @UseTotp BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[Organization] + SET + [Name] = @Name, + [BusinessName] = @BusinessName, + [BusinessAddress1] = @BusinessAddress1, + [BusinessAddress2] = @BusinessAddress2, + [BusinessAddress3] = @BusinessAddress3, + [BusinessCountry] = @BusinessCountry, + [BusinessTaxNumber] = @BusinessTaxNumber, + [BillingEmail] = @BillingEmail, + [Plan] = @Plan, + [PlanType] = @PlanType, + [Seats] = @Seats, + [MaxCollections] = @MaxCollections, + [UseGroups] = @UseGroups, + [UseDirectory] = @UseDirectory, + [UseTotp] = @UseTotp, + [SelfHost] = @SelfHost, + [UsersGetPremium] = @UsersGetPremium, + [Storage] = @Storage, + [MaxStorageGb] = @MaxStorageGb, + [Gateway] = @Gateway, + [GatewayCustomerId] = @GatewayCustomerId, + [GatewaySubscriptionId] = @GatewaySubscriptionId, + [Enabled] = @Enabled, + [LicenseKey] = @LicenseKey, + [ExpirationDate] = @ExpirationDate, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate + WHERE + [Id] = @Id +END +GO + +IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'OrganizationUserOrganizationDetailsView') +BEGIN + DROP VIEW [dbo].[OrganizationUserOrganizationDetailsView] +END +GO + +CREATE VIEW [dbo].[OrganizationUserOrganizationDetailsView] +AS +SELECT + OU.[UserId], + OU.[OrganizationId], + O.[Name], + O.[Enabled], + O.[UseGroups], + O.[UseDirectory], + O.[UseTotp], + O.[SelfHost], + O.[UsersGetPremium], + O.[Seats], + O.[MaxCollections], + O.[MaxStorageGb], + OU.[Key], + OU.[Status], + OU.[Type] +FROM + [dbo].[OrganizationUser] OU +INNER JOIN + [dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] +GO diff --git a/util/Setup/Setup.csproj b/util/Setup/Setup.csproj index d5df65ae8..c15bfe2da 100644 --- a/util/Setup/Setup.csproj +++ b/util/Setup/Setup.csproj @@ -8,6 +8,7 @@ +